|
|
Este artigo está disponível em: English Castellano Deutsch Francais Nederlands Portugues 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 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:
|
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:
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][...][¶m_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=20De facto, é muito mais simples do que parece. Analisemos este URL. :
www.linuxdoc.org
;/cgi-bin/ldpsrch.cgi
;?
é o principio de uma longa lista de argumentos :
srv=http%3A%2F%2Fwww.linuxdoc.org
é o servidor de onde o pedido vem;srch=ssh
representa o pedido em si;db=1
significa que o pedido só diz respeito aos HOWTOs;scope=0
significa que o pedido diz respeito ao conteúdo dos documentos e não somente ao título;rpt=20
limita a 20 o número de respostas apresentadas.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.
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 :
Options Includes
activa qualquer SSI ;OptionsIncludesNoExec
proíbe exec cmd
e exec cgi
.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 < e > no código HTML. Uma pessoa curiosa podia submeter uma das instruções:
<!--#printenv -->
(ignore o espaço a seguir ao printenv
)<!--#exec cmd="cat /etc/passwd"-->
guestbook.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/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.
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 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;
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%2FpasswdSó 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|
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 :
;
:termina a instrução e salta para a próxima;&&
: se a primeira instrução tiver sucesso (i.e. retorna 0 na shell), então a próxima é executada;||
: se a primeira instrução falhar (i.e. retorna um valor não nulo na shell), então a próxima é executada.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/passwdSã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/passwdA shell divide isto em duas instruções diferentes :
/usr/bin/finger kmaster\
que provavelmente falhará ... mas não nos importamos ;-)cat /etc/passwd
a que apresenta o ficheiro passwd.\
' deve também estar protegida.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%22A 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.
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;
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 :
open(FILE,"<$filename") || ...
para leitura somente;open(FILE,">$filename") || ...
para escrita somenteAntes 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()
.
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 :
$safe
contém os caracteres considerados seguros (aqui, números e letras somente);$danger
contém os caracteres a serem removidos, visto que são permitidos mas potencialmente perigosos.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.
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.
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 | > |
man perlsec
: Página do manual (man) de Perl acerca de segurança;#!/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); }
|
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:
|
2001-12-19, generated by lfparser version 2.21