Forçar o download de um arquivo - Melhorado e bug corrigido (Funciona no IE 6)
Prefácio
Há alguns posts atrás eu sugeri uma rotina para forçar o download de arquivos, porém uma delas tem um sério problema se não for implementada com uma verificação de segurança.Confesso que eu não havia percebido este equívoco e agradeço ao leitor Luiz F. G. Deitos por ter me questionado acerca desta vulnerabilidade no script.
Ah... E a melhor parte do novo script: funciona no IE 6.
Conceito
A vulnerabilidade se baseia no fato de que você pode passar um caminho de arquivo malicioso para o script, fazendo com que sejam baixados outros arquivos, que não os que o programador especifica.Muito cuidado agora: O que pode acontecer se for requisitada a página index.php, ou até mesmo algo como "../conexao.php".
Como os dados vêm crus, você tem acesso a todos os includes, podendo assim fazer a varredura dos arquivos que são requisitados e também os seus respectivos caminhos, podendo depois baixa-los.
Como deter? Ta na hora de resolver este problema! ;)
Mão na massa
O que temos de fazer é bastante simples. Vamos criar um script que verifica se existe alguma string do tipo ./ (diretório atual) e ../ (diretório acima).Caso um destes dois esteja presente, indicamos ao usuário que isto não é permitido.
Vou explicar novamente, pois vou tirar o artigo anterior do ar.
Ao recebermos o arquivo via get (download.php?arquivo=baixar.jpg), vamos coloca-lo em $arquivo.
Em seguida verificamos se não existem retornos de diretório, conforme comentado anteriormente, e em seguida se o arquivo realmente existe (file_exists).
Se tudo correr bem, enviamos um header indicando que o arquivo deverá ser baixado (octet/stream, ou seja, um executável).
O Content-disposition irá indicar que é um arquivo do tipo anexo e qual é o seu nome. Já o Content-length indica o tamanho do arquivo.
readfile(), por fim, envia o arquivo para o navegador.
Qualquer cabeçalho que seja enviado a mais irá causar um bug no IE6 onde não é mostrado o nome do arquivo na hora do download.
Para evitar que o fulano tente baixar arquivos de nosso diretório atual, vamos colocar todos os downloads em outro diretório. Para tanto, criaremos uma constante chamada DIR_DOWNLOAD e vamos definir um diretório para os arquivos.
Importante: Neste diretório coloque APENAS arquivos de download, para evitar que o usuário baixe arquivos que não deve.
Código: Alternar entre o modo de cópia/destaque
<?php $arquivo = $_GET['arquivo']; if (stripos($arquivo, './') !== false || stripos($arquivo, '../') !== false || !file_exists($arquivo)) $arquivo = DIR_DOWNLOAD.$arquivo; // Aqui a gente só junta o diretório com o nome do arquivo exit; ?>
<?php
define('DIR_DOWNLOAD', '../downloads/'); // Aqui vale qualquer coisa :)
$arquivo = $_GET['arquivo'];
if (stripos($arquivo, './') !== false || stripos($arquivo, '../') !== false || !file_exists($arquivo))
exit('Operação não permitida.');
$arquivo = DIR_DOWNLOAD.$arquivo; // Aqui a gente só junta o diretório com o nome do arquivo
header('Content-type: octet/stream');
header('Content-disposition: attachment; filename="'.basename($arquivo).'";');
header('Content-Length: '.filesize($arquivo));
readfile($arquivo);
exit;
?>
Salve este arquivo com um nome simples, por exemplo download.php.
Para executar um download, envie os dados para a variável arquivo do GET.
Um exemplo de link para baixar a imagem exemplo.jpg ficaria assim:
Conclusão
Agora temos um script mais seguro e mais bacana!Agradeço a todos que têm entrado e comentado, inclusive fazendo críticas sobre o código e sugerindo melhoras.
Um grande abraço a todos e fiquem com Deus!
PHP levado a sério