Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en Georges Tarbouriech
en to en Lorne Bailey
en to pt Bruno Sousa
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.
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); }