PHPit - PHP

"Agora, pois, permanecem a fé, a esperança, o amor, estes três; mas o maior destes é o amor." (I Coríntios 13:13)

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
  1. <?php
  2. define('DIR_DOWNLOAD', '../downloads/'); // Aqui vale qualquer coisa :)
  3.  
  4. $arquivo = $_GET['arquivo'];
  5. if (stripos($arquivo, './') !== false || stripos($arquivo, '../') !== false || !file_exists($arquivo))
  6. exit('Operação não permitida.');
  7.  
  8. $arquivo = DIR_DOWNLOAD.$arquivo; // Aqui a gente só junta o diretório com o nome do arquivo
  9.  
  10. header('Content-type: octet/stream');
  11. header('Content-disposition: attachment; filename="'.basename($arquivo).'";');
  12. header('Content-Length: '.filesize($arquivo));
  13. readfile($arquivo);
  14. ?>
<?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:

<a href="download.php?arquivo=exemplo.jpg">Baixar a imagem</a>


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!

Dim dim

 

Comentaí! (11)

Grande Jaques!

Show o script.
Acabo de implementá-lo numa aplicação.

Abraços! Carlos Tristacci em 22/12/2008 às 16:53 utilizando o Mozilla Firefox Mozilla Firefox 3.0.5 no Mac OS Mac OS X
Eu dei uma melhorada nesse script...

Espero que tenha ficado bom :) Rafael Jaques em 10/01/2009 às 11:36 utilizando o Opera Opera 9.30 no Nintendo Wii Nintendo Wii
Opa, blz?
Show de bola... Agora está mais legal ;o)...
Fiz à fim de testes (obviamente), entrei em um site de uma empresa grande de Bento Gonçalves, e encontrei um código parecido, bem simplificado. Apliquei o Hack e estava eu com um arquivo php no meu desktop contendo o usuário e senha do DB...
ruim isso hem,,, vou alertar o proprietário da empresa de tal ocorrência...

abração! Luiz F. G. Deitos em 23/01/2009 às 15:10 utilizando o Mozilla Firefox Mozilla Firefox 2.0.0.20 no Windows Windows XP
Cara, tava varrendo a net e não tinha sucesso. Agora consegui com o seu script.
Vlw! Guilherme em 01/06/2009 às 17:54 utilizando o Mozilla Firefox Mozilla Firefox 3.0.10 no Fedora Linux Fedora Linux
como eu faço para pegar o downloads direto do mysql ? filipe mattevi vailoes em 24/06/2009 às 16:25 utilizando o Internet Explorer Internet Explorer 8.0 no Windows Windows Vista
olá o arquivo corronpe quando eu fasso o dowloads!

voce sabe porque ? filipe em 26/06/2009 às 17:56 utilizando o Mozilla Firefox Mozilla Firefox 3.5 no Windows Windows Vista
Me basiei no seu post para fazer o meu, no meu caso, passo um arquivo fixo para fazer download e disparo um e-mail =P Wellington em 23/07/2009 às 15:34 utilizando o Mozilla Firefox Mozilla Firefox 3.0.12 no Windows Windows XP
Estou com um pequeno problema! Ao final do envio a pagina aparece meio q em codigo criptografada e o arquivo não aparece p ser salvo..........sera q alguem sabe o motivo.......
Obrigado! sergio_a em 01/09/2009 às 13:58 utilizando o Internet Explorer Internet Explorer 8.0 no Windows Windows XP
O script esta com um erro. Ele verifica se o arquivo existe, mas como esta em outro diretorio ele nunca vai achar o arquivo e sempre vai dar a mensagem de erro.

Deve-se concatenar o diretorio e o nome do arquivo antes da verificação. Diogo em 25/11/2009 às 12:52 utilizando o Mozilla Firefox Mozilla Firefox 3.5.5 no Windows Windows XP
Hahahahah foi divertido achar o PHP It procurando o que eu precisava no Google!

Valeu, Rafa! Marta @suco_de_uva em 16/01/2010 às 13:29 utilizando o Safari Safari 531.21.10 no Mac OS Mac OS X
Demorou mas encontrei!
rsrs...
Excelente artigo, muito bem explicado.

abraço! Rodrigo em 18/03/2010 às 08:36 utilizando o Mozilla Firefox Mozilla Firefox 3.6 no Windows Windows NT
 

Comenta logo, pô!

* Todos os links inseridos nos comentários possuem rel="nofollow" para impedir com que crawlers considerem os mesmos como relevantes.
* Os e-mails não são divulgados.

Dados pessoais

Você é realmente um humano?

Finalmente, digite seu comentário :)

Caixinha de Sugestões

O que é isso? Aqui você pode simplesmente digitar uma sugestão (artigos, resenhas, melhorias, etc) sem precisar preencher longos formulários. Digite o que quiser na caixinha abaixo e eu lerei com o maior prazer! Se quiser se identificar, fique a vontade!

O PHPit é redigido e mantido por Rafael Jaques - Política de Privacidade.

XHTML/CSS desenvolvido por André Gazola.

PHPit 2007 - 2009 - Alguns direitos reservados.

Feeds ;)

Creative Commons License

PHPit por Rafael Jaques é licensiado sob a Creative Commons Atribuição-Uso Não-Comercial-Compartilhamento pela mesma Licença 2.5 Brasil License.