Implicações de segurança da execução de perl -ne '…' *

27

Aparentemente, executando:

perl -n -e 'some perl code' *

Ou

find . ... -exec perl -n -e '...' {} +

(o mesmo com em -pvez de -n)

Ou

perl -e 'some code using <>' *

freqüentemente encontrado em one-liners postadas neste site, tem implicações de segurança. Qual é o problema? Como evitá-lo?

Stéphane Chazelas
fonte

Respostas:

33

Qual é o problema

Primeiro, como em muitos utilitários, você terá um problema com os nomes de arquivo começando com -. Enquanto estiver em:

sh -c 'inline sh script here' other args

Os outros argumentos são passados ​​para o inline sh script; com o perlequivalente

perl -e 'inline perl script here' other args

Os outros argumentos são verificados para obter mais opções para perl primeiro, não para o script embutido. Então, por exemplo, se houver um arquivo chamado -eBEGIN{do something evil}no diretório atual,

perl -ne 'inline perl script here;' *

(com ou sem -n) fará algo mau.

Como em outros utilitários, a solução alternativa é usar o marcador de fim de opções ( --):

perl -ne 'inline perl script here;' -- *

Mas, mesmo assim, ainda é perigoso e isso depende do <>operador usado por -n/ -p.

O problema é explicado na perldoc perlopdocumentação.

Esse operador especial é usado para ler uma linha (um registro, registros sendo linhas por padrão) da entrada, onde essa entrada é proveniente de cada um dos argumentos passados ​​por sua vez @ARGV.

Em:

perl -pe '' a b

-pimplica um while (<>)loop em torno do código (aqui vazio).

<>abrirá primeiro a, lerá os registros uma linha de cada vez até que o arquivo esteja esgotado e, em seguida, abrirá b...

O problema é que, para abrir o arquivo, ele usa a primeira forma insegura de open:

open ARGV, "the file as provided"

Com esse formulário, se o argumento for

  • "> afile", abre afileno modo de escrita,
  • "cmd|", ele roda cmde lê sua saída.
  • "|cmd", você tem um fluxo aberto para gravação na entrada de cmd.

Então, por exemplo:

perl -pe '' 'uname|'

Não gera o conteúdo do arquivo chamado uname|(um nome de arquivo perfeitamente válido, btw), mas a saída do unamecomando.

Se você estiver executando:

perl -ne 'something' -- *

E alguém criou um arquivo chamado rm -rf "$HOME"|(novamente um nome de arquivo perfeitamente válido) no diretório atual (por exemplo, porque esse diretório já foi gravável por outras pessoas, ou você extraiu um arquivo desonesto ou executou algum comando desonesto, ou outra vulnerabilidade em outro software foi explorada), então você está com um grande problema. As áreas em que é importante estar ciente desse problema são as ferramentas que processam arquivos automaticamente em áreas públicas como /tmp(ou ferramentas que podem ser chamadas por essas ferramentas).

Arquivos chamados > foo, foo|, |foosão um problema. Porém, em menor grau < fooe foocom caracteres de espaçamento ASCII iniciais ou finais (incluindo espaço, tab, nova linha, cr ...), isso significa que esses arquivos não serão processados ​​ou o errado.

ǖLembre-se também de que alguns caracteres em alguns conjuntos de caracteres de vários bytes (como no BIG5-HKSCS) terminam no byte 0x7c, a codificação de |.

$ printf ǖ | iconv -t BIG5-HKSCS | od -tx1 -tc
0000000  88  7c
        210   |
0000002

Portanto, nas localidades que usam esse conjunto de caracteres,

 perl -pe '' ./nǖ

Iria tentar executar o ./n\x88comando como perlse não tentar interpretar o nome do arquivo na localidade do usuário!

Como corrigir / contornar

AFAIK, não há nada que você possa fazer para alterar esse comportamento padrão inseguro de perluma vez por todas em todo o sistema.

Primeiro, o problema ocorre apenas com caracteres no início e no final do nome do arquivo. Então, enquanto perl -ne '' *ouperl -ne '' *.txt é um problema,

perl -ne 'some code' ./*.txt

não é porque todos os argumentos agora começar com ./e terminam em .txt(por isso não -, <, >, |, espaço ...). De modo mais geral, é uma boa idéia para prefixo globs com ./. Isso também evita problemas com arquivos chamados -ou iniciados com- muitos outros utilitários (e aqui, isso significa que você não precisa --mais do marcador de fim de opções ( )).

Usar -Tpara ativar o taintmodo ajuda até certo ponto. Ele irá abortar o comando se tal arquivo malicioso for encontrado (apenas para o >e| casos , não <ou espaço em branco).

Isso é útil ao usar esses comandos de maneira interativa, alertando que há algo desonesto. No entanto, isso pode não ser desejável ao fazer algum processamento automático, pois isso significa que alguém pode fazer isso. processamento falhe apenas criando um arquivo.

Se você deseja processar todos os arquivos, independentemente do nome, pode usar o ARGV::readonly perlmódulo no CPAN (infelizmente, normalmente não é instalado por padrão). Esse é um módulo muito curto que faz:

sub import{
   # Tom Christiansen in Message-ID: <24692.1217339882@chthon>
   # reccomends essentially the following:
   for (@ARGV){
       s/^(\s+)/.\/$1/;   # leading whitespace preserved
       s/^/< /;       # force open for input
       $_.=qq/\0/;    # trailing whitespace preserved & pipes forbidden
   };
};

Basicamente, ele limpa o @ARGV, transformando, " foo|"por exemplo, em "< ./ foo|\0".

Você pode fazer o mesmo em uma BEGINdeclaração em seu perl -n/-pcomando:

perl -pe 'BEGIN{$_.="\0" for @ARGV} your code here' ./*

Aqui, simplificamos a suposição que ./está sendo usada.

Um efeito colateral disso (e ARGV::readonly) é que$ARGV em your code heremostra que arrasta caractere NUL.

Atualização 2015-06-03

perlA v5.21.5 e superior têm um novo <<>>operador que se comporta como <>exceto que ele não fará esse processamento especial. Os argumentos serão considerados apenas como nomes de arquivo. Portanto, com essas versões, agora você pode escrever:

perl -e 'while(<<>>){ ...;}' -- *

(não esqueça --ou use./* ) sem medo de sobrescrever arquivos ou executar comandos inesperados.

-n/ -pAinda usam o perigoso <>forma embora. E tenha em atenção que os links simbólicos ainda estão sendo seguidos, de modo que não significa necessariamente que seja seguro usá-lo em diretórios não confiáveis.

Stéphane Chazelas
fonte
2
você trabalhou nisso o dia todo, aposto. bem feito.
mikeserv
2
boa atualização para o perl, mas é estranho que os desenvolvedores do perl não tenham adicionado as opções -P e -N (não é possível alterar o existente -p e -n porque alguns scripts podem confiar no comportamento inseguro)
cas 21/07
9

Além da resposta de @ Stéphane Chazelas , não precisamos nos preocupar com esse problema se usarmos a -iopção de linha de comando:

$ perl -pe '' 'uname|'
Linux

$ perl -i -pe '' 'uname|'
Can't open uname|: No such file or directory.

Porque ao usar a -iopção, perlusei stat para verificar o status do arquivo antes de processá-lo:

$ strace -fe trace=stat perl -pe '' 'uname|'
stat("/home/cuonglm/perl5/lib/perl5/5.20.1/x86_64-linux", 0x7fffd44dff90) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/5.20.1", 0x7fffd44dff90) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/x86_64-linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
Process 6106 attached
Linux
Process 6105 suspended
Process 6105 resumed
Process 6106 detached
--- SIGCHLD (Child exited) @ 0 (0) ---

$ strace -fe trace=stat perl -i -pe '' 'uname|'
stat("/home/cuonglm/perl5/lib/perl5/5.20.1/x86_64-linux", 0x7fffdbaf2e50) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/5.20.1", 0x7fffdbaf2e50) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/x86_64-linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
stat("uname|", 0x785f40)                = -1 ENOENT (No such file or directory)
Can't open uname|: No such file or directory.
cuonglm
fonte
1
Não existe uma possível condição de corrida entre a statverificação e o processamento perl efetivo logo após?
Totor
@ Motor: Eu acho que não.
cuonglm
Não é sobre isso stat. É apenas -ipara editar arquivos no local, para que não faça sentido aceitar argumentos que não sejam caminhos de arquivos reais; portanto -i, com isso , o processamento especial não é feito.
Stéphane Chazelas