|
|
Este artículo está disponible en los siguientes idiomas: English Castellano Deutsch Francais Nederlands Portugues Russian Turkce |
por Frédéric Raynal, Christophe Blaess, Christophe Grenier <pappy(at)users.sourceforge.net, ccb(at)club-internet.fr, grenier(at)nef.esiea.fr> Sobre el autor: Christophe Blaess es un ingeniero aeronáutico independiente. Es un fan de Linux y realiza mucho de su trabajo con este sistema. Además, coordina la traducción de las páginas del manual publicadas en Linux Documentation Project. Christophe Grenier es un estudiante de 5º curso en ESIEA, donde trabaja como administrador de sistemas. Siente pasión por la seguridad en computadoras. Frédéric Raynal ha estado utilizando Linux durante muchos años ya que no contamina, no utiliza hormonas, ni GMO ni harina de engorde para animales... solamente contiene esfuerzo e ingenio. Taducido al español por: Gerard Farràs Ballabriga <gerard.farras(at)campus.uab.es> Contenidos:
|
Resumen:
Obteniendo un fichero, ejecutando un script en Perl mal programado
... "Hay más de un camino para hacer esto!"
Artículos previos de esta serie :
Cuando un cliente pide un archivo HTML, el servidor envía la página pedida
(o un mensaje de error). El navegador interpreta el código HTML para formatear y visualizar
el fichero. Para poner un ejemplo, escribiendo la URL (Uniform Request Locator)
http://www.linuxdoc.org/HOWTO/HOWTO-INDEX/howtos.html
,
el cliente se conecta al servidor
www.linuxdoc.org
y pide la página
/HOWTO/HOWTO-INDEX/howtos.html
, utilizando el protocolo HTTP.
Si la página existe, el servidor envía el archivo pedido.
Con este modelo estático, si el archivo está presente en el servidor
, éste es enviado "tal y como es" al cliente, de otra forma un mensaje de error
es enviado (el bien conocido 404 - Not Found).
Desgraciadamente, esto no permite la interactividad con el usuario. De este modo cosas como e-negocios, e-reservas para vacaciones o e-loquesea no es posible.
Afortunadamente, hay soluciones para generar dinámicamente páginas HTML. Los scripts CGI (Common Gateway Interface) son una de ellas. En este caso, la URL para acceder a estas páginas es construida de forma ligeramente diferente:
http://<servidor><pathHaciaScript>[?[param_1=val_1][...][¶m_n=val_n]]La lista de argumentos es guardada en la variable de entorno
QUERY_STRING
.
En este contexto, un script CGI no es nada más que un archivo ejecutable.
Utiliza stdin
(standard input)
o la variable de entorno QUERY_STRING
para obtener
los argumentos que le pasan. Después de ejecutar el código, el resultado es
mostrado en stdout
(standard output) y luego,
redirigido al cliente web. Casi todos los lenguajes de programación pueden ser usados
para escribir un script CGI (un programa compilado en C, Perl,
shell-scripts...). Por ejemplo, permítame buscar qué es lo que los HOWTOs de
www.linuxdoc.org
conocen sobre ssh :
http://www.linuxdoc.org/cgi-bin/ldpsrch.cgi? svr=http%3A%2F%2Fwww.linuxdoc.org&srch=ssh&db=1&scope=0&rpt=20De hecho, esto es mucho más simple de lo que parece. Vamos a analizar esta URL :
www.linuxdoc.org
;/cgi-bin/ldpsrch.cgi
;?
es el comienzo de una larga lista de argumentos
:
srv=http%3A%2F%2Fwww.linuxdoc.org
es el servidor
de donde viene la petición;srch=ssh
contiene la petición en sí;db=1
significa que la petición solo se refiere a
HOWTOs;scope=0
significa que la petición se refiere al contenido
del documento y no solo a su título;rpt=20
limita a 20 el número de respuestas visualizadas.
Frecuentemente, los nombres de los argumentos y sus valores son suficientemente explícitos como para entender su significado. Además, el contenido de la página que muestra las respuestas puede ser significativas.
Ahora sabemos que el lado brillante de los scripts CGI es la habilidad que tiene un usuario para pasar argumentos... pero el lado oscuro es que un script mal programado abre un agujero de seguridad.
Probablemente habrás notado caracteres extraños en tu navegador o presentes dentro de la petición previa. Estos caracteres están en formato Unicode. La tabla 1 muestra el significado de algunos de estos códigos. Permíteme mencionar que algunos servidores IIS4.0 y IIS5.0 tienen una vulnerabilidad basada en estos caracteres.
SSI Server Side
Include
"Server Side Include
es una función parte de los servidores web.
Permite integrar instrucciones dentro de las páginas web, incluir un fichero "tal y como es", o ejecutar un comando
(shell o script CGI).
En el fichero de configuración del Apache httpd.conf
, la instrucción
"AddHandler server-parsed .shtml
" activa este mecanismo.
Frecuentemente, para evitar la distinción entre .html
and .shtml
, uno puede añadir la
extensión .html
. Evidentemente, esto ralentiza el servidor...
Esto puede ser controlado a nivel de directorios con las instrucciones:
Options Includes
activa todos los SSI ;OptionsIncludesNoExec
prohibe exec
cmd
y exec cgi
.En el script adjunto LibroDeInvitados.cgi
, el texto proporcionado por el usuario
es incluido en un archivo HTML, sin la conversión de caracteres de '<' y '>'
hacia los códigos HTML < and > . Suficiente para que una
persona curiosa envie una de las siguientes
instrucciones :
<!--#printenv -->
(recuerda el espacio después de
printenv
)<!--#exec cmd="cat /etc/passwd"-->
LibroDeInvitados.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
DOCUMENT_ROOT=/home/web/sites/www8080 HTTP_ACCEPT=image/gif, image/jpeg, image/pjpeg, image/png, */* HTTP_ACCEPT_CHARSET=iso-8859-1,*,utf-8 HTTP_ACCEPT_ENCODING=gzip HTTP_ACCEPT_LANGUAGE=en, fr HTTP_CONNECTION=Keep-Alive HTTP_HOST=www.esiea.fr:8080 HTTP_PRAGMA=no-cache HTTP_REFERER=http://www.esiea.fr:8080/~grenier/cgi/LibroDeInvitados.cgi? email=&texte=%3C%21--%23include+file%3D%22LibroDeInvitados.cgi%22--%3E HTTP_USER_AGENT=Mozilla/4.76 [fr] (X11; U; Linux 2.2.16 i686) PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin REMOTE_ADDR=194.57.201.103 REMOTE_HOST=nef.esiea.fr REMOTE_PORT=3672 SCRIPT_FILENAME=/mnt/c/nef/grenier/public_html/cgi/LibroDeInvitados.html SERVER_ADDR=194.57.201.103 SERVER_ADMIN=master8080@nef.esiea.fr SERVER_NAME=www.esiea.fr SERVER_PORT=8080 SERVER_SIGNATURE=<ADDRESS>Apache/1.3.14 Server www.esiea.fr Port 8080</ADDRESS> SERVER_SOFTWARE=Apache/1.3.14 (Unix) (Red-Hat/Linux) PHP/3.0.18 GATEWAY_INTERFACE=CGI/1.1 SERVER_PROTOCOL=HTTP/1.0 REQUEST_METHOD=GET QUERY_STRING= REQUEST_URI=/~grenier/cgi/LibroDeInvitados.html SCRIPT_NAME=/~grenier/cgi/LibroDeInvitados.html DATE_LOCAL=Tuesday, 27-Feb-2001 15:33:56 CET DATE_GMT=Tuesday, 27-Feb-2001 14:33:56 GMT LAST_MODIFIED=Tuesday, 27-Feb-2001 15:28:05 CET DOCUMENT_URI=/~grenier/cgi/LibroDeInvitados.shtml DOCUMENT_PATH_INFO= USER_NAME=grenier DOCUMENT_NAME=LibroDeInvitados.html
La instrucción exec
, suministra casi lo mismo que una shell
:
LibroDeInvitados.cgi?email=ppy&texte=%3c%21--%23exec%20cmd="cat%20/etc/passwd"%20--%3e
No intentes "<!--#include
file="/etc/passwd"-->
", el path es relativo al directorio donde
puedes encontrar el fichero HTML y no puede contener "..
".
El fichero de Apache error_log
, contendrá un mensaje indicando un intento de acceso a un fichero prohibido
. El usuario podrá ver el mensaje [an error occurred while
processing this directive]
en la página HTML.
SSI no es necesario frecuentemente y es mejor desactivarlo del servidor. Sin embargo, la causa del problema es la combinación entre la aplicación mal programada LibroDeInvitados y SSI.
En esta sección, vamos a presentar los agujeros de seguridad relacionados con los scripts CGI escritos en Perl. Para hacerlo mas claro, no daremos todo el código sino solamente las partes requeridas para entender el problema.
Cada uno de nuestros scripts está programado siguiendo la plantilla siguiente :
#!/usr/bin/perl -wT BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Hacemos %ENV más segura =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD>"; print "<TITLE>Comando remoto</TITLE></HEAD>\n"; &ReadParse(\%input); # Podemos usar $input por ejemplo así: # print "<p>$input{archivo}</p>\n"; # ########################################## # # Principio de la descripción del problema # # ########################################## # # #################################### # # Fin de la descripción del problema # # #################################### # form: print "<form action=\"$ENV{ÑOMBRE_DEL_SCRIPT'}\">\n"; print "<input type=texto name=archivo>\n </form>\n"; print "</BODY>\n"; print "</HTML>\n"; exit(0); # el primer argumento tiene que ser una referencia a un hash # El hash será rellenado con datos. sub ReadParse($) { my $in=shift; my ($i, $key, $val); my $in_primero; my @in_segundo; # Leer en texto if ($ENV{'REQUEST_METHOD'} eq "GET") { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_primero,$ENV{'CONTENT_LENGTH'}); }else{ die "ERROR: Método de petición desconocido\n"; } @in_segundo = split(/&/,$in_primero); foreach $i (0 .. $#in_segundo) { # Convertir los caracteres + en espacios $in_segundo[$i] =~ s/\+/ /g; # Partir entre la clave y el valor. ($key, $val) = split(/=/,$in_segundo[$i],2); # Convertir %XX de números hexadecimales a alfanuméricos $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Asociar una clave con un valor # \0 es el separador múltiple $$in{$key} .= "\0" if (defined($$in{$key})); $$in{$key} .= $val; } return length($#in_segundo); }
Después vamos a aprender más sobre los argumentos pasados al intérprete Perl (-wT
).
Empezamos limpiando las variables de entorno $ENV
y $PATH
y enviamos seguidamente la cabecera HTML (esto es parte del protocolo html implementado por el navegador cliente y
el servidor. Aunque no podamos verlo desde el lado del cliente).
La función ReadParse()
lee los argumentos pasados al script.
Esto puede hacerse más facilmente con módulos, pero así podemos ver el código entero.
Ahora vamos a presentar algunos ejemplos.
Perl considera cada carácter de la misma forma, cosa que difiere de las funciones en C, por ejemplo. En Perl, el carácter null al final de una cadena (string) es como cualquier otro. Qué implica eso ?
Vamos a añadir el siguiente código a nuestro script para crear
showhtml.cgi
:
# showhtml.cgi my $archivo= $input{archivo}.".html"; print "<BODY>Archivo : $archivo<BR>"; if (-e $archivo) { open(FILE,"$archivo") || goto form; print <FILE>; }
La función ReadParse()
obtiene el único argumento :
el nombre del archivo a mostrar. Para prevenir que se pueda leer algo más que ficheros HTML,
añadimos la extensión ".html
" al final del nombre del fichero. Pero,
recuerda, el carácter null es como cualquiera otro...
De este modo, si nuestra petición es
showhtml.cgi?archivo=%2Fetc%2Fpasswd%00
el archivo es llamado
my $archivo = "/etc/passwd\0.html"
y nuestros pasmados ojos estarán mirando algo
que no parece HTML.
Qué es lo que pasa ? El comando strace
nos muestra como Perl
abre un fichero:
/tmp >>cat >open.pl << EOF > #!/usr/bin/perl > open(FILE, "/etc/passwd\0.html"); > EOF /tmp >>chmod 0700 open.pl /tmp >>strace ./open.pl 2>&1 | grep open execve("./open.pl", ["./open.pl"], [/* 24 vars */]) = 0 ... open("./open.pl", O_RDONLY) = 3 read(3, "#!/usr/bin/perl\n\nopen(FILE, \"/et"..., 4096) = 51 open("/etc/passwd", O_RDONLY) = 3
El último open()
presentado por strace
corresponde a la llamada de sistema escrita en C. Como podemos ver la extensión
.html
ha desaparecido, y esto nos permite abrir /etc/passwd.
Este problema es solucionado con una expresión regular muy simple que borre todos los caracteres null:
s/\0//g;
Aquí tenemos un script sin protección alguna que muestra un archivo especificado del árbol de directorios /home/httpd/ :
#pipe1.cgi my $archivo= "/home/httpd/".$input{archivo}; print "<BODY>File : $archivo<BR>"; open(FILE,"$archivo") || goto form; print <FILE>;
No vayais a reíros con este ejemplo! He visto cosas parecidas en muchos scripts.
El primer exploit es obvio :
pipe1.cgi?archivo=..%2F..%2F..%2Fetc%2FpasswdSuficiente para "subir" al árbol de directorios y acceder a cualquier fichero. Pero hay algo mucho más interesante : ejecutar el comando que tú escojas. En Perl, el comando
open(FILE, "/bin/ls")
abre el archivo binario
"/bin/ls
" ... pero
open(FILE, "/bin/ls |")
ejecuta dicho comando.
Añadiendo un simple pipe |
cambia la conducta de open()
. Otro problema viene del hecho que la existencia del archivo no es testeada, esto nos permite ejecutar cualquier comando y además pasarle argumentos:
pipe1.cgi?archivo=..%2F..%2F..%2Fbin%2Fcat%20%2fetc%2fpasswd%20|muestra el contenido del fichero de passwords.
Testeando la existencia del archivo da menos libertad a open :
#pipe2.cgi my $archivo= "/home/httpd/".$input{archivo}; print "<BODY>File : $archivo<BR>"; if (-e $archivo) { open(FILE,"$archivo") || goto form; print <FILE> } else { print "-e fallado: el fichero no existe\n"; }Aquí el ejemplo anterior ya no funciona. El test "
-e
"
falla ya que no encuentra el archivo "../../../bin/cat
/etc/passwd |
". Vamos a probar el comando /bin/ls
. Su conducta será la misma que antes.
Eso es, si intentamos, por ejemplo de listar el contenido del directorio
/etc
, "-e
"
testea la existencia del fichero "../../../bin/ls /etc |
",
que no existe. Como no suministremos el nombre de un fichero "fantasma" no vamos a obtener
nada interesante :(
Sin embargo, aún hay alguna alternativa.
El fichero /bin/ls
existe (en la mayoría de los sistemas) y pasaría el chequeo,
pero si open()
es llamado con este nombre de archivo
se mostraría el archivo binario, pero no se ejecutaría. Debemos buscar una solución para
poner una tubería (pipe) '|
' al final del nombre,
pero que no sea testeado con "-e
". Ya conocemos la solución : el byte null. Si enviamos
"../../../bin/ls\0|
" , el test de existencia pasa ya que solo considera
"../../../bin/ls
", pero open()
puede ver el pipe
y luego ejecuta el comando. Así que la URL que suministra el contenido del directorio actual es:
pipe2.cgi?archivo=../../../bin/ls%00|
El script finger.cgi ejecuta la instrucción finger
en nuestra máquina :
#finger.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; $CMD= "/usr/bin/finger $login|"; open(FILE,"$CMD") || goto form; print <FILE>
Este script utiliza, como mínimo, una protección útil: tiene en cuenta algunos caracteres extraños
para prevenir que sean interpretados por la shell poniendo un '\
' delante. Así, el punto y coma
es cambiado a "\;
" por la expresión regular. Pero la lista no contiene todos los caracteres importantes.
Entre otros, el salto de línea '\n
'.
En tu shell preferida puedes validar una instrucción pulsando la tecla
RETURN
o ENTER
, que envía el carácter '\n
'. En Perl, puedes hacer lo mismo.
Ya hemos visto como la instrucción open()
nos permite ejecutar un comando cuando la línea termina con un pipe
'|
'.
Para simular este comportamiento es suficiente con añadir un salto de línea y una instrucción , después de enviar el login al comando finger :
finger.cgi?login=kmaster%0Acat%20/etc/passwd
Hay otros caracteres interesantes para ejecutar varias instrucciones en una sola línea:
;
: acaba con la primera instrucción y va a ejecutar la próxima
;&&
: si la primera instrucción no falla
(por ejemplo, devuelve un 0), la próxima va a ser ejecutada;
||
: si la primera instrucción falla
(por ejemplo, devuelve un valor no null), luego la próxima es ejecutada.El script finger.cgi
previo evita problemas con algunos caracteres extraños.
Así, la URL
<finger.cgi?login=kmaster;cat%20/etc/passwd
no resulta ya que el punto y coma es
evitado. Sin embargo, hay un carácter que no está protegido: la barra invertida '\
'.
Imaginemos por un momento un script que evite la ascensión en el árbol de directorios
utilizando la expresión regular s/\.\.//g
para desembarazarnos de "..
".
No importa! Las shells pueden manejar varios '/
' a la vez (simplemente prueba cat
///etc//////passwd
para quedar convencido).
Por ejemplo, en el script anterior pipe2.cgi
, la variable
$fichero
es inicializada con el prefijo
"/home/httpd/
". Parece que usando esta expresión regular
sería suficiente para evitar la ascensión en el árbol de directorios.
Evidentemente, esta expresión protege a
"..
", pero qué pasa si nosotros protegemos el carácter
'.
' ? Eso es, la expresión regular
no concuerda si el nombre del fichero es .\./.\./etc/passwd
.
En realidad, esta cadena funciona bien con la llamada system()
(o
con ` ... `
), pero falla con open()
o el test "-e
".
Vamos atrás con el script finger.cgi
. Utilizando el punto y coma
la URL finger.cgi?login=kmaster;cat%20/etc/passwd
no da el resultado esperado ya que
el punto y coma es filtrado por la expresión regular. Eso es, la shell recibe la instrucción:
/usr/bin/finger kmaster\;cat /etc/passwdLos siguientes errores son encontrados en los logs del servidor web :
finger: kmaster;cat: no such user. finger: /etc/passwd: no such user.Estos mensajes son idénticos a los que obtendrías si lo escribieses en una shell. El problema viene del hecho que la shell considera el carácter protegido '
;
' como parte de la cadena "kmaster;cat
" . Necesitamos separar las dos instrucciones, la primera para el script y
la siguiente que queremos ejecutar. Debemos proteger
';
' :
<A HREF="finger.cgi?login=kmaster\;cat%20/etc/passwd">
finger.cgi?login=kmaster\;cat%20/etc/passwd</A>
. La cadena
"\;
es cambiada en el script por
"\\;
", y luego, enviada a la shell. Éste lee lo siguiente :
/usr/bin/finger kmaster\\;cat /etc/passwdLa shell divide la cadena en dos:
/usr/bin/finger kmaster\
que probablemente fallará... no sufras por eso ;-)cat /etc/passwd
que mostrará el fichero de passwords.
\
' también debe ser filtrado.
A veces, el parámetro es "protegido" con comillas. Hemos cambiado ligeramente
el script previo finger.cgi
para proteger la variable
$login
.
Sin embargo, si las comillas no son filtradas sera una consideración inútil. Suficiente con añadir una en nuestra petición. Así, la primera comilla " enviada, cierra la abierta por el script. Luego, escribes el comando, y la segunda comilla abriendo la última (que cerraría) del script.
El script finger2.cgi ilustra la idea :
#finger2.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/\0//g; $login =~ s/([<>\*\|`&\$!#\(\)\[\]\{\}:'\n])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; #Nueva (in)eficiente super protección : $CMD= "/usr/bin/finger \"$login\"|"; open(FILE,"$CMD") || goto form; while(<FILE>) { print; }
La URL que ejecutará el comando se convierte en :
finger2.cgi?login=kmaster%22%3Bcat%20%2Fetc%2Fpasswd%3B%22La shell recibe el comando
/usr/bin/finger "$login";cat
/etc/passwd""
y las comillas ya no serán un problema. Recuerda que si quieres proteger los parámetros de tu script con comillas, debes filtrarlos igual que la barra invertida o el punto y coma.
Cuando programemos en Perl es aconsejable la opción w
o
"use warnings;
" (Perl 5.6.0 y siguientes) ya que nos informará sobre problemas
potenciales como variables no inicializadas o expresiones/funciones obsoletas.
La opción T
(taint mode) proporciona mayor seguridad.
Este modo activa varios tests.
El más importante concierne a una posible corrupción (tainting) de las variables.
Las variables pueden estar limpias o posiblemente corruptas.
Los datos que provienen del exterior del programa son considerados corruptos
hasta que no hayan sido limpiadas.
Las variables que pueden ser corruptas no se pueden asignar a objetos que serán usados
en el exterior del programa (llamadas a otros comandos de la shell).
En taint mode, los argumentos de la línea de comandos, las variables de entorno,
algunos resultados de llamadas de sistema (readdir()
,
readlink()
, readdir()
, ...) y los datos que provienen de archivos,
son considerados sospechosos y por lo tanto son vigilados.
Para limpiar una variable debes pasarla a través de una expresión regular.
Evidentemente, utilizar .*
es inútil. El objetivo
es forzarte a tener en cuenta los argumentos proporcionados.
Debemos intentar utilizar siempre una expresión regular lo más específica posible.
No obstante, este modo no protege de todo: no se vigilan los argumentos pasados
a system()
o exec()
como una lista de variables.
Debemos ser muy cuidadosos cuando uno de nuestros scripts utilize estas funciones.
Las instrucciones exec "sh", '-c', $arg;
son consideradas como seguras,
esté como esté $arg
:(
También es recomendado "use strict;" al principio de tus programas.
Así forzamos la obligación de declarar todas las variables; algunas personas
pueden encontrarlo molesto pero es obligatorio cuando utilizemos
mod-perl
.
De este modo, tus scripts en Perl deben empezar así :
#!/usr/bin/perl -wT use strict; use CGI;con Perl 5.6.0 :
#!/usr/bin/perl -T use warnings; use strict; use CGI;
open()
Muchos programadores abren un fichero simplemente utilizando
open(FILE,"$fichero") || ...
. Ya hemos visto los riesgos
de este código. Para reducir este riesgo es suficiente con especificar el modo
para abrirlo:
open(FILE,"<$fichero") || ...
para solo lectura;open(FILE,">$fichero") || ...
para solo escritura
Antes de acceder a un fichero es recomendable chequear si éste existe. Esto no evita las condiciones de carrera presentadas en el artículo anterior, pero es útil respecto a algunos trucos como comandos con sus argumentos.
if ( -e $fichero ) { ... }
Desde la versión de Perl 5.6, hay una nueva sintaxis para la llamada
open()
: open(FILEHANDLE,MODO,LISTA)
. Con
el modo '<' , el fichero se abre para lectura; con '>'
, el fichero es truncado o creado si es necesario, y abierto para escritura.
Hay un par de modos muy interesantes para la comunicación con otros procesos.
Si el modo es '|-' o '-|', el argumento LISTA es interpretado como un comando y es
respectivamente encontrado antes o después de la tubería.
Antes de Perl 5.6 y open()
con tres argumentos,
algunas personas utilizaban el comando sysopen()
.
Existen dos métodos : Podemos especificar los caracteres prohibidos, o definir explícitamente los carecteres permitidos utilizando expresiones regulares. Los programas de ejemplo te habrán convencido que es muy fácil olvidarse de filtrar caracteres que pueden ser potencialmente peligrosos, es por esta razón que el segundo método es recomendado.
Prácticamente, lo que hacemos es lo siguiente : primero, chequeamos si la petición contiene sólo caracteres permitidos. Luego, filtramos los caracteres considerados como peligrosos entre los permitidos.
#!/usr/bin/perl -wT # filtro.pl # Las variables $seguro y $peligroso definen respectivamente # los caracteres sin riesgo y los arriesgados. # Es suficiente con añadir/quitar algunos para cambiar el filtro. # Solamente la entrada $input que contenga los caracteres incluídos en # las definiciones es válida. use strict; my $input = shift; my $seguro = '\w\d'; my $peligroso = '&`\'\\|"*?~<>^(){}\$\n\r\[\]'; #Note: # '/', espacio y tab no son parte de las definiciones if ($input =~ m/^[$seguro$peligroso]+$/g) { $input =~ s/([$peligroso]+)/\\$1/g; } else { die "Hay caracteres no permitidos en la entrada $input\n"; } print "input = [$input]\n";
Este script define dos conjuntos de caracteres :
$seguro
contiene los caracteres no prohibidos
(en este caso, sólo números y letras);$peligroso
contiene los caracteres permitidos, pero que
pueden ser potencialmente peligrosos; deberán ser filtrados.
No querría ser polémico pero creo que es mejor escribir scripts
en PHP que no en Perl. Más exactamente, como administrador de sistemas,
prefiero que mis usuarios escriban scripts con lenguaje PHP que no en Perl.
Cualquiera que programe mal - o de forma insegura - en PHP puede dejar un agujero
de seguridad igual de peligroso que en Perl.
Si es así, porqué prefiero PHP? Pués porqué en este lenguaje puedes activar
un Modo Seguro (Safe mode) cuando haya problemas de programación
(safe_mode=on
) o desactivar funciones peligrosas
(disable_functions=...
). Este modo impide
acceder a ficheros que no sean propiedad del usuario, o cambiar variables de entorno
sin que esté explícitamente permitido, ejecutar comandos, etc.
Por defecto el banner de Apache nos informa sobre la versión de PHP que estamos usando.
$ telnet localhost 80 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. HEAD / HTTP/1.0 HTTP/1.1 200 OK Date: Tue, 03 Apr 2001 11:22:41 GMT Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a PHP/4.0.4pl1 mod_perl/1.24 Connection: close Content-Type: text/html Connection closed by foreign host.Suficiente con escribir
expose_PHP = Off
en
/etc/php.ini
para esconder dicha información :
Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a mod_perl/1.24
El fichero /etc/php.ini
(PHP4) o
/etc/httpd/php3.ini
tiene muchos parámetros que permiten
hacer el sistema más robusto.
Por ejemplo,
la opción "magic_quotes_gpc
" añade unas comillas en los argumentos
recibidos por los métodos GET
, POST
y vía cookies;
esto soluciona algunos problemas de seguridad que nos hemos
encontrado con Perl.
Entre los artículos de esta serie, éste es probablemente el más fácil de entender.
Nos enseña vulnerabilidades que pueden ser explotadas cada día en la web.
Hay muchas otras, frecuentemente relacionadas con mala programación
(por ejemplo, un script que envía correo electrónico, que coge como argumento
el campo From:
, proporciona un buen lugar para enviar spam
(correo no deseado). Los ejemplos son muy numerosos. En seguida que hay un script
en un sitio web, habrá como mínimo una persona que intentará usarlo de forma fraudulenta.
Este artículo termina la serie sobre programación segura. Esperamos haberte mostrado los principales agujeros de seguridad presentes en muchas aplicaciones y que a partir de ahora tengas en cuenta el factor "seguridad" cuando diseñes y programes tus aplicaciones. Los problemas de seguridad son muchas veces olvidados debido al limitado propósito de la aplicación (uso interno, uso en una red privada, modelo temporal, etc.). Sin embargo, un módulo originariamente diseñado para un uso muy restringido puede convertirse en la base de una aplicación mucho mayor, y luego los cambios serán más caros.
Unicode | Carácter |
%00 | \0 (final de cadena) |
%0a | \n (salto de carro) |
%20 | espacio |
%21 | ! |
%22 | " |
%23 | # |
%26 | & (ampersand) |
%2f | / |
%3b | ; |
%3c | < |
%3e | > |
man perlsec
: Página del manual de Perl sobre seguridad;#!/usr/bin/perl -w # LibroDeInvitados.cgi BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Hacemos %ENV más seguro =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD><TITLE>Libro De Visitas Peligroso </TITLE></HEAD>\n"; &ReadParse(\%input); my $email= $input{email}; my $texto= $input{texto}; $texto =~ s/\n/<BR>/g; print "<BODY><A HREF=\"LibroDeInvitados.html\"> GuestBook </A><BR><form action=\"$ENV{'SCRIPT_NAME'}\">\n Email: <input type=texto name=email><BR>\n Texte:<BR>\n<textarea name=\"texto\" rows=15 cols=70> </textarea><BR><input type=submit value=\"Adelante!\"> </form>\n"; print "</BODY>\n"; print "</HTML>"; open (FILE,">>LibroDeInvitados.html") || die ("No es posible la escritura\n"); print FILE "Email: $email<BR>\n"; print FILE "Texto: $texto<BR>\n"; print FILE "<HR>\n"; close(FILE); exit(0); sub ReadParse { my $in =shift; my ($i, $key, $val); my $in_first; my @in_second; # Read in text if ($ENV{'REQUEST_METHOD'} eq "GET") { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); }else{ die "ERROR: Método de petición desconocido\n"; } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Convertir los + en espacios $in_second[$i] =~ s/\+/ /g; # Dividir entre la clave y el valor. ($key, $val) = split(/=/,$in_second[$i],2); # Convertir los números hexadecimales %XX a alfanuméricos $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Asociar una clave con su valor. $$in{$key} .= "\0" if (defined($$in{$key})); $$in{$key} .= $val; } return length($#in_second); }
|
Contactar con el equipo de LinuFocus
© Frédéric Raynal, Christophe Blaess, Christophe Grenier, FDL LinuxFocus.org Pinchar aquí para informar de algún problema o enviar comentarios a LinuxFocus |
Información sobre la traducción:
|
2001-11-23, generated by lfparser version 2.21