r6 - 22 Feb 2007 - AlceuJunior
Criando programas em Perl de forma segura
Técnicas para criar programas em Perl de forma segura.Motivações
Este texto tem a intenção de servir com um tutorial de como escrever programas em Perl que eventualmente precisem executar um outro programa, seja para capturar a saída do mesmo ou depender de alguma atividade que ele executa e você não quer reproduzir isso em código Perl por diversos motivos. Eu escrevi este texto por dois motivos basicamente: 1 - Cansei de ver mensagens na lista sobre scripts ruins disponíveis no Matt's script archive (se você está procurando um repositório de scripts prontos então evite o Matt Script Archive, aonde você encontra exemplos realmente bons de como não programar em Perl; uma alternativa muito melhor é o projeto NMS). 2 - Perl é uma linguagem famosa como glue language no entanto não existe nenhum documento em português sobre o como executar outros programas de forma segura (pelo menos até o término deste artigo). Este texto foi redigido originalmente no Vim, um programa felizmente de uso livre. Nenhum bit foi ferido durante o processo.Programação segura
Programar de forma segura é, acima de tudo, um hábito. Mesmo que a linguagem de programação ofereça recursos para programar desta forma, geralmente isso é opcional. Na realidade, existem poucos casos em que você talvez não precise se preocupar com isso. Vou citar dois exemplos em que talvez isso funcione: 1 - Você desenvolve em Perl apenas para você (seu egoísta!). 2 - Você trabalha sozinho e é o único ser vivo, racional, que tem acesso ao computador aonde seus programas em Perl residem. Em outros casos você deveria se preocupar sobre o quanto seu programa é seguro para ser usado. Usuários comentem bobagens às vezes por descuido ou inocência. Outras vezes as cometem por maldade mesmo. Prever o dano que seu programa pode causar se usado de forma errônea é um risco que você sempre deve analisar caso seja responsável pelos programas que escreve.O básico
Existem dois cuidados básicos para qualquer programa que você for escrever em Perl:- Sempre verifique a entrada de dados feitas pelo usuário ou por outro programa que execute seu script Perl;
my $entrada = shift;
system("cat /etc/passwd | grep $entrada");
Esse é um exemplo ingênuo, mas funcional. Isso mostraria uma ou mais linhas de respostas baseadas no critério de pesquisa do comando grep. Agora imagine que a entrada do usuário seria essa:
# programa.pl > /etc/shadow'Assim que o Perl substitui-se o valor da variável $entrada, você teria o conteúdo do arquivo de senhas substituído pelo conteúdo de /etc/passwd. Isso provavelmente seria algo prejudicial para seu programa e para o sistema em si.
- Sempre procure encontrar módulos em Perl no CPAN que possam estar executando a tarefa que você quer realizar. Isso evita chamadas ao sistema, o que é mais seguro e muitas vezes mais rápido. Supondo que você seja preguiçoso (e isso é uma virtude para programadores, segundo Larry Wall) você não quer reinventar a roda: quer apenas resolver seu problema. Isso é louvável. Mas procure sempre encontrar um substituto escrito em Perl para o programa que você queira rodar. Para tarefas simples e corriqueiras, como executar um 'tail' no shell, existe muitos módulos em Perl que já lhe oferecem isso, e estão disponíveis no CPAN. Tudo que você tem que fazer é baixar e instalar esses módulos (vide perldoc CPAN para maiores informações de como fazer isso). Tendo essas tarefas sendo executadas pelo próprio interpretador do Perl, você não precisa fazer chamadas de sistemas, o que é mais rápido (na maioria das vezes), seguro e limpo.
Revendo as técnicas básicas
Existem diversas formas que se pode utilizar em Perl para executar programas de sistema:- a crase;
- system;
- open;
- exec;
- IPC::Open2;
- IPC::Open3
Não usarás!
Algum comandos (ou uma de suas formas de utilização) vão chamar o shell para interpretar e executar o programa. Isso inclui a interpretação de coisas como "*", "?", "<", ">", etc... Você deve se lembrar o quanto esses metacaracteres podem ser perigosos. Existem algumas coisas que você simplesmente não poderá usar quando quiser escrever um programa seguro:- crase
$var = `who`Mas usar a crase significa que a Perl sempre vai chamar o shell para interpretar o comando e devolver a resposta.
- system com apenas um argumento
system("algum comando para executar");
Quando chamado dessa forma, a Perl passa novamente seu comando para o shell interpretar, incluindo os parâmetros.
- open
Usarás (e com cuidado)!
- Os comandos listados abaixo não usam a shell, então você deve considerar usá-los sempre que lhe for possível.
system("comando","argumento1","argumento2");
o system não chama mais o shell para interpretar o comando, sendo executado diretamente pela Perl.
- A dobradinha open e exec
open(FOO, '|-') || exec 'tr', '[a-z]', '[A-Z]'; open(FOO, '-|') || exec 'cat', '-n', $file;Respectivamente, direcionando a saída do programa Perl para o programa tr e redirecionando a saída do cat para seu programa em Perl.
- usando o exec
- IPC::Open2
use IPC::Open2; # Exemplo 1 # essa é a forma incorreta de se evitar o shell! $pid = open2(\*RDRFH, \*WTRFH, 'some cmd and args'); # Exemplo 2 # esta é a forma correta $pid = open2(\*RDRFH, \*WTRFH, 'some', 'cmd', 'and', 'args');
- IPC::Open3
use IPC::Open3; use strict; use warnings; my($wtr, $rdr, $err); $pid = open3($wtr, $rdr, $err,'some cmd and args', 'optarg', ...);Onde $wtr, $rdr e $err são respectivamente STDIN, STDOUT e STDERR do programa sendo executado. Particularmente eu gosto do open3 para ter maior controle sobre a aplicação sendo executada: normalmente o programa pode usar STDOUT e STDERR para emitir mensagens e elas podem ser necessárias para controle do script ou ainda precisam ser omitidas. Ao invés de trabalhar com STDIN, STDOUT e STDERR de forma separada, você pode simplesmente fazer assim:
use IPC::Open3;
use strict;
use warnings;
my $pid = open3(\*WTRFH,\*FROMCHILD,\*FROMCHILD,"ps","ax");
while ( <FROMCHILD> ) {print;}
close(FROMCHILD);
close(WTRFH);
Entendeu o truque? Estamos usando STDOUT e STDERR com a mesma referência, o que nos permite ler os dois handles de uma vez só.
Outros cuidados
Existem muitos cuidados que você ter quando quiser escrever um programa Perl de modo que ele funcione com uma relativa segurança. Algumas coisas são relativas a cuidados de arquitetura, como race conditions. Outros são cuidados mínimos, como usar a opção "Taint" do Perl. A opção Taint funciona como uma pragma do Perl, assim como warnings e strict. Você pode invocar o modo Taint dessa forma:#!/usr/bin/perl -Tou ainda assim:
# perl -T script_perlQuando o modo Taint for ativado, o Perl vai assumir que quaisquer outros dados vindos de fora do programa, como variáveis definidas pela entrada do usuário em tempo de execução, são marcadas como corrompidas e o programa é interrompido no ato quando é tentado usar essa variável para coisas perigosas, como definir um nome de arquivo a ser criado. Para resolver isso, você deve "lavar" os dados para que a Perl passe a aceitá-lo:
use strict;
use warnings;
my $arquivo = shift;
unless ($arquivo =~ /(^[\w\.\-\_]+$)/) {
die "Nome de arquivo inválido\n";
} else {
$arquivo = $1;
}
Essas linhas somente permitirão que um nome de arquivo seja composto apenas de letras, números, "-", "_" e pontos ("."). Qualquer outra coisa é tida como inaceitável (como um ">") e o programa é abortado.
Você sempre tem que "lavar" os dados dessa maneira e dessa forma o modo Taint vai aceitar a variável caso ela não cai na ocorrência de possuir um caracter indesejado. É importante cuidar para que a expressão regular realmente elimine qualquer outra coisa que não o aceitável. Se você tentar enganar o modo Taint, vai apenas enganar a si mesmo.
O modo Taint também o ajuda a evitar uma porção de outras coisas também. Procure ler sua documentação perldoc perlsec para maiores detalhes de como essa pragma pode lhe ajudar.
Usar o modo Taint às vezes pode ser algo chato de se fazer. Afinal, você pode ter que começar a lavar uma porção de variáveis que antes não estavam causando nenhum problema em seu script. Mas fazer algo bem feito exige esforço, então tente não ser preguiçoso nesse sentido.
Bibligrafia
- perldoc -f open
- perldoc IPC::Open2
- perldoc IPC::Open3
- perldoc -f select
- perldoc IO::Select
- perldoc perlsec
