[LinuxFocus-icon]
Início  |  Mapa  |  Índice  |  Procura

Novidades | Arquivos | Links | Sobre LF
Este artigo está disponível em: English  Castellano  Deutsch  Francais  Nederlands  Portugues  Russian  Turkce  

convert to palmConvert to GutenPalm
or to PalmDoc

[image of the authors]
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 o autor:

O Christophe Blaess é um engenheiro aeronáutico independente. Ele é um fã do Linux e faz muito do seu trabalho neste sistema. Coordena a tradução das páginas man publicadas no Projecto de Documentação do Linux.

O Christophe Grenier é um estudante no 5º ano na ESIEA, onde, também trabalha como administrador de sistema. Tem uma paixão por segurança de computadores.

O Frederic Raynal tem utilizado o Linux desde há alguns anos porque não polui, não usa hormonas, não usa MSG ou farinha animal ... reclama somente o suor e a astúcia.



Traduzido para Português por:
Bruno Sousa <bruno(at)linuxfocus.org>

Conteúdo:

 

Evitando falhas de Segurança ao desenvolver uma aplicação - Parte 6: 6: scripts CGI

[article illustration]

Abstrato:

Obter um ficheiro, correndo um programa a partir de uma scripts mal programada ... " Há mais do que uma maneira de o fazer ! "

Artigos Anteriores, desta série:



 

Servidor Web, URL e problemas de configuração

 

Introdução (breve) como um servidor web funciona e como construir um URL

Quando um cliente pede um ficheiro HTML, o servidor envia o ficheiro pedido (ou uma mensagem de erro). O browser interpreta o código HTML para formatar e apresentar o ficheiro. Por exemplo digitando o URL (Uniform Request Location): http://www.linuxdoc.org/HOWTO/ HOWTO-INDEX/howtos.html o cliente liga-se ao servidor www.linuxdoc.org e pede a página /HOWTO/HOWTO-INDEX/howtos.html, utilizando o protocolo HTTP. Se a página existe o servidor envia o ficheiro pedido. Com este modelo estático, se o ficheiro está presente no servidor, é enviado "tal e qual" para o cliente, caso contrário é enviada uma mensagem de erro (o bastante conhecido 404 - Not Found).

Infelizmente, isto não permite interactividade com o utilizador, fazendo com que inovações como o e-business, o e-reservation para férias ou o e-qualquer coisa seja impossível.

Felizmente, existem soluções para gerar páginas HTML dinamicamente. As Scripts CGI (Common Gateway Interface) são uma delas. Neste caso, O URL para aceder às páginas web é construído de uma maneira um pouco diferente :

http://<server><pathToScript>[?[param_1=val_1][...][&param_n=val_n]]
A lista de argumentos é guardada na variável de ambiente QUERY_STRING. Neste contexto, uma script CGI não é mais do que um ficheiro executável. Utiliza a stdin (entrada de dados padrão - standard input) ou a variável de ambiente QUERY_STRING para obter os argumentos passados. Após executar o código o resultado é apresentado na stdout (saída de dados padrão - standard output) e depois, redireccionada para o cliente web. Praticamente todas as linguagens de programação podem ser usadas para escrever um script CGI (programas C compilados, Perl, scripts da shell...).

Por exemplo, procuremos o que os HOWTOs em www.linuxdoc.org sabem acerca do ssh :

http://www.linuxdoc.org/cgi-bin/ldpsrch.cgi?
  svr=http%3A%2F%2Fwww.linuxdoc.org&srch=ssh&db=1&scope=0&rpt=20
De facto, é muito mais simples do que parece. Analisemos este URL. :

Muitas vezes, os nomes dos argumentos são bastante explícitos para se compreender o seu significado. E acrescente-se que o conteúdo da página a apresentar a resposta é mais significativo.

Agora sabem que o lado bom das scripts CGI é a habilidade do utilizador em passar argumentos... mas o lado negro é que uma script mal escrita abre um buraco na segurança.

Provavelmente, reparou nos caracteres estranhos utilizados pelo seu browser preferido no pedido acima. Estes caracteres estão no formato URL codificado (não confunda isto com unicode). A tabela 1 fornece o significado de tais códigos. Mencionemos que os servidores IIS4.0 e IIS5.0 têm vulnerabilidades baseadas nestes caracteres.

 

Configuração do Apache com "SSI Server Side Include"

O Server Side Include faz parte da funcionalidade de um servidor web. Permite a integração de instruções nas páginas web, ou incluir um ficheiro "as is", ou executar um comando (shell ou script CGI).

No ficheiro de configuração do Apache httpd.conf, a instrução "AddHandler server-parsed .shtml" activa este mecanismo. Por vezes para evitar a distinção entre .html e .shtml, adiciona-se à última a extensão .html. Claro que isto atrasa o servidor... Isto pode ser controlado ao nível dos directórios com as instruções :



Na script guestbook.cgi em anexo, o texto fornecido por um utilizador está incluído num ficheiro HTML, sem a conversão dos caracteres '<' e ' >' para &lt; e &gt; no código HTML. Uma pessoa curiosa podia submeter uma das instruções:

Com o primeiro,
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
obtém algumas linhas contendo informação acerca do sistema :
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/guestbook.cgi?
 email=&texte=%3C%21--%23include+file%3D%22guestbook.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/guestbook.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/guestbook.html
SCRIPT_NAME=/~grenier/cgi/guestbook.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/guestbook.shtml
DOCUMENT_PATH_INFO=
USER_NAME=grenier
DOCUMENT_NAME=guestbook.shtml



A instrução exec é como que um equivalente da shell :



guestbook.cgi?email=ppy&texte=%3c%21--%23exec%20cmd="cat%20/etc/passwd"%20--%3e

Não tente "<!--#include file="/etc/passwd"-->", o caminho é relativo ao directório onde pode encontrar o ficheiro HTML e não pode conter "..". O ficheiro do Apache error_log contém depois uma mensagem indicando uma tentativa de acesso a um ficheiro proibido. O utilizador pode ver a mensagem [an error occurred while processing this directive] na página HTML.

O SSI, na generalidade, não é preciso por isso é melhor desactivá-lo no servidor. Contudo a causa do problema é a combinação de uma má aplicação do guestbook com o SSI.

 

Scripts em Perl

Nesta secção, apresentamos buracos de segurança relacionados com as scripts CGI escritas em Perl. Para manter as coisas claras, não fornecemos todo o código mas somente as partes necessárias para entender onde se encontra o problema.

Cada uma das nossas scripts é construída segundo o modelo seguinte :

#!/usr/bin/perl -wT
BEGIN { $ENV{PATH} = '/usr/bin:/bin' }
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};   # Make %ENV safer =:-)
print "Content-type: text/html\n\n";
print "<HTML>\n<HEAD>";
print "<TITLE>Remote Command</TITLE></HEAD>\n";
&ReadParse(\%input);
# now use $input e.g like this:
# print "<p>$input{filename}</p>\n";
# #################################### #
# Start of problem description         #
# #################################### #



# ################################## #
# End of problem description         #
# ################################## #

form:
print "<form action=\"$ENV{'SCRIPT_NAME'}\">\n";
print "<input type=texte name=filename>\n </form>\n";
print "</BODY>\n";
print "</HTML>\n";
exit(0);

# first arg must be a reference to a hash.
# The hash will be filled with data.
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: unknown request method\n";
  }

  @in_second = split(/&/,$in_first);

  foreach $i (0 .. $#in_second) {
    # Convert plus's to spaces
    $in_second[$i] =~ s/\+/ /g;

    # Split into key and value.
    ($key, $val) = split(/=/,$in_second[$i],2);

    # Convert %XX from hex numbers to alphanumeric
    $key =~ s/%(..)/pack("c",hex($1))/ge;
    $val =~ s/%(..)/pack("c",hex($1))/ge;

    # Associate key and value
    # \0 is the multiple separator
    $$in{$key} .= "\0" if (defined($$in{$key}));
    $$in{$key} .= $val;

  }
  return length($#in_second);
}

Mais acerca dos argumentos passados ao Perl (-wT) mais tarde. Começamos por limpar as variáveis de ambiente $ENV e $PATH e enviamos o cabeçalho de HTML (isto é algo que faz parte do protocolo entre o browser e o servidor. Não o consegue ver do lado do cliente). A função ReadParse() lê os argumentos passados à script. Isto podia ser feito mais facilmente com módulos, mas deste modo pode ver todo o código. De seguida apresentamos exemplos. Por último acabamos o ficheiro HTML.

 

O byte Nulo

O Perl considera cada caracter do mesmo modo, o que difere das funções C, por exemplo. Para o perl o caracter nulo para terminar uma string é como outro qualquer. E então ?

Adicionemos o seguinte código à nossa script para criar o showhtml.cgi  :

  # showhtml.cgi
  my $filename= $input{filename}.".html";
  print "<BODY>File : $filename<BR>";
  if (-e $filename) {
      open(FILE,"$filename") || goto form;
      print <FILE>;
  }


A função ReadParse() obtém o único argumento : O nome do ficheiro a apresentar. Para prevenir alguns "convidados rudes" de ler para além dos ficheiros HTML, adicionamos a extensão ".html" no fim do ficheiro. Mas lembre-se que o byte nulo é um caracter como outro qualquer...

Assim, se o nosso pedido é showhtml.cgi?filename=%2Fetc%2Fpasswd%00 o ficheiro chama-se my $filename = "/etc/passwd\0.html" e os nossos olhos ficam pasmados com algo que não é HTML.

O que acontece ? O comando strace mostra como o Perl abre um ficheiro:

  /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


O último open() apresentado pelo strace corresponde a uma chamada de sistema escrita em C. Podemos ver que a extensão .html desapareceu e isto permitiu-nos abrir o ficheiro /etc/passwd.

Este problema resolve-se com uma simples expressão regular que remove os bytes nulos:

s/\0//g;


 

Utilizando pipes

Eis aqui uma script sem qualquer protecção. Apresenta um dado ficheiro na árvore da directoria /home/httpd/ :

#pipe1.cgi

my $filename= "/home/httpd/".$input{filename};
print "<BODY>File : $filename<BR>";
open(FILE,"$filename") || goto form;
print <FILE>;


Não se ria com este exemplo ! Eu vi scripts assim.

A primeira exploração é óbvia :

pipe1.cgi?filename=..%2F..%2F..%2Fetc%2Fpasswd
Só precisa de subir na árvore de directoria para aceder a qualquer ficheiro. Mas existe ainda uma possibilidade mais interessante : a execução de um comando à sua escolha. Em Perl, o comando open(FILE, "/bin/ls") abre o ficheiro binário "/bin/ls"... mas o open(FILE, "/bin/ls |") executa o comando especificado. O facto de se adicionar um simples pipe | altera o comportamento do open().

Outro problema vem do facto da existência do ficheiro não ser testado, o que nos permite executar qualquer comando mas também passar os argumentos : pipe1.cgi?filename=..%2F..%2F..%2Fbin%2Fcat%20%2fetc%2fpasswd%20| o que apresenta o conteúdo do ficheiro password.

Testar a existência da abertura de um ficheiro dá menos liberdade :

#pipe2.cgi

my $filename= "/home/httpd/".$input{filename};
print "<BODY>File : $filename<BR>";
if (-e $filename) {
  open(FILE,"$filename") || goto form;
  print <FILE>
} else {
  print "-e failed: no file\n";
}
O exemplo anterior já não trabalha mais. O teste "-e" falha se não encontra o ficheiro "../../../bin/cat /etc/passwd |".

Tentemos, agora o comando /bin/ls. O comportamento será como antes. Ou seja, se por exemplo, tentarmos listar o conteúdo do directório /etc, o teste "-e" verifica a existência do ficheiro "../../../bin/ls /etc | mas também não existe. A não ser que demos o nome de um ficheiro "fantasma" não conseguiremos nada interessante :(

Contudo, existe ainda um modo de "contornar" a situação, mesmo que o resultado não seja tão bom. O ficheiro /bin/ls existe (bem, na maioria dos sistemas), mas se o open() é chamado com o nome do ficheiro o comando não é executado, mas é apresentado o ficheiro em binário. Temos então de encontrar um modo de pôr um pipe '|' no fim do nome, sem que seja usado na verificação feita por "-e". Já sabemos a solução : o byte nulo. Se enviarmos "../../../bin/ls\0|" como nome, o teste da existência tem sucesso visto que só considera "../../../bin/ls", mas o open() consegue ver o pipe e executar o comando. Então o URL fornecendo o conteúdo do directório corrente é:

pipe2.cgi?filename=../../../bin/ls%00|
 

Avanço de Linha

A script finger.cgi executa a instrução finger na sua 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>


Esta script, (pelo menos) toma uma precaução útil : toma cuidado com caracteres estranhos para prevenir que estes sejam interpretados pela shell colocando uma '\' à frente dos caracteres. Assim, a semicoluna é alterada para "\;" por uma expressão regular. Mas a lista não contém todos os caracteres importantes. Entre outros o avanço de linha '\n' está em falta.

Se preferir a linha de comandos da shell, você valida uma instrução carregando RETURN ou ENTER que envia o caracter '\n'. Em Perl, pode fazer o mesmo. Já vimos a instrução open() que nos permitia executar um comando logo que a linha terminasse com um pipe '|'.

Para simular este comportamento adicionamos um carriage-return e uma instrução após o login enviado ao comando finger :

finger.cgi?login=kmaster%0Acat%20/etc/passwd


Outros caracteres são bastante interessantes para executar várias instruções numa célula :



Elas não trabalham aqui, visto que estão protegidas pela expressão regular. Mas vamos encontrar um modo de pôr isto a funcionar.  

A Backslash e a semicoluna

A script anterior, finger.cgi evita problemas com caracteres estranhos. Então o URL <finger.cgi?login=kmaster;cat%20/etc/passwd não trabalha quando a semicoluna está protegida. Contudo, existe um caracter que não está protegido : a backslash '\'.

Tomemos, por exemplo, uma script que nos previne de subir na árvore utilizando expressões regulares s/\.\.//g para nos livrarmos de "..". Não importa ! As shells conseguem lidar com vários números de '/' uma só vez (tente cat ///etc//////passwd para ficar convencido).

Por exemplo, na script acima pipe2.cgi, a variável $filename é inicializada a partir do prefixo "/home/httpd/". Utilizando a expressão regular anterior podia parecer eficiente a prevenção de subir nos directórios. Claro que esta expressão protege-o de "..", mas o que é que acontece se protegermos o caracter '.'? Ou seja, a expressão não condiz se o nome do ficheiro é : .\./.\./etc/passwd. Mencionemos, que isto trabalha bem com system() (ou ` ... `), mas o open() ou o "-e" falham.

Voltemos à script finger.cgi. Utilizando a semicoluna, o URL finger.cgi?login=kmaster;cat%20/etc/passwd não dá o resultado esperado visto que a semicoluna está protegida pela expressão regular. Ou seja, a shell recebe a instrução:

/usr/bin/finger kmaster\;cat /etc/passwd
São encontrados os seguintes erros no servidor web :
finger: kmaster;cat: no such user.
finger: /etc/passwd: no such user.
As mensagens são idênticas aquelas que obtém quando digita esta linha na shell. O problema vem do facto do protegido caracter ';' ser considerado como pertencente à string "kmaster;cat" .

Queremos separar ambas as instruções, uma da script e aquela que queremos utilizar. Devemos, então proteger ';' : <A HREF="finger.cgi?login=kmaster\;cat%20/etc/passwd"> finger.cgi?login=kmaster\;cat%20/etc/passwd</A>. A string "\;, é então alterada pela script para "\\;", e depois, enviada à shell que lê : :

/usr/bin/finger kmaster\\;cat /etc/passwd
A shell divide isto em duas instruções diferentes :
  1. /usr/bin/finger kmaster\ que provavelmente falhará ... mas não nos importamos ;-)
  2. cat /etc/passwd a que apresenta o ficheiro passwd.
A solução é simples : a backslash '\' deve também estar protegida.

 

Utilizando um caracter " desprotegido

Por vezes o parâmetro, está "protegido" utilizando aspas. Alterámos a script anterior finger.cgi para proteger a variável $login deste modo.

Contudo, se as aspas não estão protegidas é inútil. Mesmo que seja adicionada o pedido falhará. Isto acontece porque a primeira aspa enviada fecha com a que abre a script. De seguida escreve o comando, e a segunda aspa fecha a última (a do fecho) aspa da script.

A script finger2.cgi ilustra isto :

#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";
#New (in)efficient super protection :
$CMD= "/usr/bin/finger \"$login\"|";
open(FILE,"$CMD") || goto form;
while(<FILE>) {
  print;
}


O URL então, a executar vem de seguida :

finger2.cgi?login=kmaster%22%3Bcat%20%2Fetc%2Fpasswd%3B%22
A shell recebe o comando /usr/bin/finger "$login";cat /etc/passwd"" e as aspas já não constituem nenhum problema.

Assim, é importante, se desejar proteger os parâmetros com aspas, deve protegê-los bem como á semicoluna e à backslash, como anteriormente referido.

 

Escrevendo em Perl

 

Avisos e opções defeituosas

Ao programar em Perl, utilize a opção w ou "use warnings;" (no Perl 5.6.0 e posteriores), informando-lhe acerca de possíveis problemas como variáveis não inicializadas ou expressões/funções obsoletas.

A opção T ( modo defeituoso) fornece ainda mais segurança. Este modo activa vários testes. O mais importante diz respeito a possíveis variáveis "defeituosas". As variáveis ou são limpas ou defeituosas. A informação que vem de fora do programa é considerada defeituosa até que seja limpa. Uma variável defeituosa não é capaz de atribuir valores a coisas que são utilizadas fora do programa (chamadas a outros comandos da shell).

No modo defeituoso os argumentos da linha de comando, as variáveis de ambiente, alguns resultados das chamadas de sistema (readdir(), readlink(), readdir(), ...) e os dados provindos de ficheiros, são considerados suspeitos, logo defeituosos.

Para limpar uma variável, deve filtrá-la através de uma expressão regular. Obviamente que a utilização de .* é inútil. O objectivo é força-lo a verificar os argumentos fornecidos. Utilize sempre uma expressão regular que deve ser especifica.

Contudo, este modo não o protege de tudo : O defeito dos argumentos passados ao system() ou exec() como uma lista de variáveis não é verificada. Deve ter bastante cuidado se uma das suas scripts utiliza estas funções. A instrução exec "sh", '-c', $arg; é considerada segura, segundo o $arg é ou não defeituoso :(

É também recomendável adicionar "use strict;" no inicio dos seus programas. Isto obriga-o a declarar as variáveis; algumas pessoas podem achar isto aborrecido mas é obrigatório se utilizar mod-perl.

Assim, se utilizar Perl CGI nas suas scripts :

#!/usr/bin/perl -wT
use strict;
use CGI;
ou com o Perl 5.6.0 :
#!/usr/bin/perl -T
use warnings;
use strict;
use CGI;


 

Chamada ao open()

Muitos programadores abrem um ficheiro utilizando open(FILE,"$filename") || .... Já vimos os riscos de tal código. Para reduzir o risco especifique o modo de abertura :

Não abra os seus ficheiros num modo não especificado.

Antes de aceder a um ficheiro é recomendável verificar se o ficheiro existe. Isto não previne os tipos de problemas das race conditions, apresentadas no artigo anterior. Mas evita armadilhas com os comandos que têm argumentos.

if ( -e $filename ) { ... }

A começar pelo Perl 5.6, existe uma nova sintaxe para o open() : open(FILEHANDLE,MODE,LIST). Como modo '<', o ficheiro é aberto para leitura; como modo '>' o ficheiro é truncado ou criado se necessário e aberto para escrita. Isto torna-se interessante para os modos de comunicar com outros processos. Se o modo for '|-' or '-|', A LISTA de argumentos é interpretada como um comando e encontra-se, respectivamente, antes do pipe.

Antes do Perl 5.6 e do open() com os três argumentos, algumas pessoas preferiam utilizar o comando sysopen().

 

Filtração e protecção dos dados de entrada

Existem dois métodos : especificar os caracteres proibidos ou definir explicitamente os caracteres permitidos, utilizando expressões regulares. Os programas de dos exemplos devem tê-lo convencido como é fácil esquecer de filtrar os potenciais caracteres perigosos, e é por isto que o segundo método é recomendado.

Eis aqui, o que, praticamente deve fazer : primeiro verifique que o pedido só contém caracteres permitidos. De seguida "remova" os caracteres considerados como perigosos de entres todos os caracteres.

#!/usr/bin/perl -wT

# filtre.pl

#  The $safe and $danger variables respectively define
#  the characters without risk and the risky ones.
#  Add or remove some to change the filter.
#  Only $input containing characters included in the
#  definitions are valid.

use strict;

my $input = shift;

my $safe = '\w\d';
my $danger = '&`\'\\|"*?~<>^(){}\$\n\r\[\]';
#Note:
#  '/', space and tab are not part of the definitions on purpose


if ($input =~ m/^[$safe$danger]+$/g) {
    $input =~ s/([$danger]+)/\\$1/g;
} else {
    die "Bad input chars in $input\n";
}
print "input = [$input]\n";


Esta script define duas definições de caracteres :

Qualquer pedido contendo caracteres não definidos nestas definições é imediatamente rejeitado.

 

Scripts PHP

Não quero ser controverso, mas penso que é melhor escrever scripts em PHP do que em Perl. Mais, exactamente, como administrador de sistema, prefiro que os meus utilizadores escrevem scripts em PHP do que em Perl. Alguém programando de um modo inseguro em PHP é tão perigoso como em Perl, mas porque é que prefiro o PHP ? Se tiver problemas de programação com o PHP pode activar o modo de segurança (safe_mode=on) ou desactivar funções (disable_functions=...). Este modo previne o acesso a ficheiros que não pertencem ao utilizador, alterar variáveis de ambiente a não ser que seja explicitamente permitido executar comandos, etc.

Por omissão, o banner do Apache informa-o acerca do PHP que está a ser utilizado.

$ 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.
Escreva expose_PHP = Off no ficheiro /etc/php.ini para esconder a informação :
Server: Apache/1.3.14 (Unix)  (Red-Hat/Linux) mod_ssl/2.7.1
OpenSSL/0.9.5a mod_perl/1.24


O ficheiro /etc/php.ini (no PHP4) e /etc/httpd/php3.ini têm muitos parâmetros que o podem ajudar a dificultar/proteger o sistema. Por exemplo a opção "magic_quotes_gpc" adiciona aspas aos argumentos recebidos pelos métodos GET, POST e através de cookies; isto evita um variado número de problemas encontrados nos nossos exemplos em Perl.

 

Conclusão

Este artigo é provavelmente, o mais fácil de entender entre os artigos desta série. Mostra-lhe vulnerabilidades exploradas todos os dias na web. Existem muitas outras relacionadas com a má programação (por exemplo uma script enviando um mail, tendo como argumento o campo From:, fornece um bom spam para um site). Os exemplos são numerosos. Logo que uma script esteja num site pode apostar que pelo menos uma pessoa tentará usá-la de um modo pervertido.

Este artigo termina a série acerca da programação segura. Esperamos que tenhamos ajudado a descobrir os principais buracos de segurança em muitas aplicações. e que levará mais em conta o parâmetro de "segurança" quando estiver a desenvolver/programar as suas aplicações. Os problemas de segurança são, por vezes, negligenciados devido à limitada abrangência do desenvolvimento (uso interno, ... uso de redes privadas, modelos temporários, etc) Contudo um modelo, originalmente desenhado para um uso muito restrito pode vir a ser a base de um aplicação muito maior, tendo-se depois de alterar o que se torna mais dispendioso.


 

Alguns caracteres URL codificados

Codificação URL Caracter
%00 \0 (fim de string)
%0a \n (carriage return)
%20 espaço
%21 !
%22 "
%23 #
%26 & (o i comercial)
%2f /
%3b ;
%3c <
%3e >
Tab 1 : Correspondência entre Unicode e caracter

 

Ligações (Links)


 

O programa guestbook.cgi defeituoso

#!/usr/bin/perl -w

# guestbook.cgi

BEGIN { $ENV{PATH} = '/usr/bin:/bin' }
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};   # Make %ENV safer =:-)
print "Content-type: text/html\n\n";
print "<HTML>\n<HEAD><TITLE>Buggy Guestbook</TITLE></HEAD>\n";
&ReadParse(\%input);
my $email= $input{email};
my $texte= $input{texte};
$texte =~ s/\n/<BR>/g;

print "<BODY><A HREF=\"guestbook.html\">
       GuestBook </A><BR><form action=\"$ENV{'SCRIPT_NAME'}\">\n
      Email: <input type=texte name=email><BR>\n
      Texte:<BR>\n<textarea name=\"texte\" rows=15 cols=70>
      </textarea><BR><input type=submit value=\"Go!\">
      </form>\n";
print "</BODY>\n";
print "</HTML>";
open (FILE,">>guestbook.html") || die ("Cannot write\n");
print FILE "Email: $email<BR>\n";
print FILE "Texte: $texte<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: unknown request method\n";
  }

  @in_second = split(/&/,$in_first);

  foreach $i (0 .. $#in_second) {
    # Convert plus's to spaces
    $in_second[$i] =~ s/\+/ /g;

    # Split into key and value.
    ($key, $val) = split(/=/,$in_second[$i],2);

    # Convert %XX from hex numbers to alphanumeric
    $key =~ s/%(..)/pack("c",hex($1))/ge;
    $val =~ s/%(..)/pack("c",hex($1))/ge;

    # Associate key and value
    $$in{$key} .= "\0" if (defined($$in{$key}));
    $$in{$key} .= $val;

  }

  return length($#in_second);
}


 

Forma de respostas para este artigo

Todo artigo tem sua própria página de respostas. Nesta página você pode enviar um comentário ou ver os comentários de outros leitores:
 página de respostas 

Páginas Web mantidas pelo time de Editores LinuxFocus
© Frédéric Raynal, Christophe Blaess, Christophe Grenier, FDL
LinuxFocus.org

Clique aqui para reportar uma falha ou para enviar um comentário para LinuxFocus
Informação sobre tradução:
fr --> -- : Frédéric Raynal, Christophe Blaess, Christophe Grenier <pappy(at)users.sourceforge.net, ccb(at)club-internet.fr, grenier(at)nef.esiea.fr>
fr --> en: Georges Tarbouriech <georges.t(at)linuxfocus.org>
en --> en: Lorne Bailey <sherm_pbody(at)yahoo.com>
en --> pt: Bruno Sousa <bruno(at)linuxfocus.org>

2001-12-19, generated by lfparser version 2.21