Forçar o download de um arquivo – Melhorado e bug corrigido (Funciona no IE 6)

! Esta publicação foi escrita há mais de dois anos. Dificilmente existirão mudanças significativas, mas tenha isso em mente ao lê-la.

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.

<?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!

28 respostas para “Forçar o download de um arquivo – Melhorado e bug corrigido (Funciona no IE 6)”

  1. Grande Jaques!

    Show o script.

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

    Abraços!

  2. Rafael Jaques disse:

    Eu dei uma melhorada nesse script…

    Espero que tenha ficado bom :)

    • Fabiano disse:

      Olá Rafael! estou com um problema em meu script de download. Ele está corrompendo arquivos .exe. Testei direto pelo link do servidor e o arquivo funcionou, mas se faço o download executando o script ele da erro ao executar o .exe.

      Se puderes me ajudar, ficaria grato!

  3. 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!

  4. Guilherme disse:

    Cara, tava varrendo a net e não tinha sucesso. Agora consegui com o seu script.

    Vlw!

  5. filipe mattevi vailo disse:

    como eu faço para pegar o downloads direto do mysql ?

  6. filipe disse:

    olá o arquivo corronpe quando eu fasso o dowloads!

    voce sabe porque ?

  7. Wellington disse:

    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

  8. sergio_a disse:

    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!

  9. Diogo disse:

    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.

  10. Hahahahah foi divertido achar o PHP It procurando o que eu precisava no Google!

    Valeu, Rafa!

  11. Rodrigo disse:

    Demorou mas encontrei!

    rsrs…

    Excelente artigo, muito bem explicado.

    abraço!

  12. André disse:

    Bom dia, parabéns pelo seu código, vou adicionar ele no meu site.

    Agora me diz uma coisa, como faço para adicionar uma função nesse script para baixar apenas arquivos do tipo .doc, .docx, pdf ??

  13. Marcio Vinicius disse:

    Show de bola o seu script, bastante funcional, apenas gostaria de resaltar aquilo que o Diogo disse ai em cima: "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."

    Então fiz umas pequenas modificações. Primeiro verifica se existe alguma ./ ou ../

    Após isso fiz "concatenei" o diretório com o arquivo e em seguida criei outro IF verificando se ele existe, ai depois disso eu faça o cabeçalho. Apenas Adaptei as minhas necessidades, e obrigado por compartilhar seus conhecimentos!! Seu Site é muito bom e extremamente útil!!

    Resultado final:
    http://pastebin.com/VHL0GUrB

  14. Hudson disse:

    Muito bom, corrigiu os erros anteriores..

    Já tentei vários scripts para download e sempre enfrento o mesmo problema.

    Em localhost todos funcionaram, mas quando hospedado no servidor (locaweb) o arquivo vem corrompido; no caso um arquivo XML e independente de seu tamanho vem sempre faltando seus 3 últimos caracteres.

    Alguém já usou esse script no locaweb ?

  15. Alciney disse:

    pô muito bom me ajudou bastante, abraços.

  16. Caíque disse:

    Só não entendi a finalidade?

    header('Content-type: octet/stream'); e o significado.. :'[

  17. Oswaldo disse:

    O script parece ser bem interessante, mas ainda vou testá-lo pra saber se vai resolver minha necessidade. Minha intenção é conseguir realizar download de arquivos de um sistema meu e isso inclui arquivos *.exe. E preciso inclusive que estes arquivos entram diretamente num determinado diretório do computador do internauta e faça substituição de arquivos se for o caso, senão não vai funcionar. Parece vírus mas não é! A ideia é dar para os meus clientes uma atualização do software que forneci atraves do meu site na internet. Ficarei muito grato se conseguir ajuda!!!

  18. O script não funciona para imagens que estão em CMYK. Existe algum modo de forçar o download de imagens em CMYK?

  19. Leo Mendes disse:

    Achei varios scripts na net pra forçar download de mp3 mas só este funcionou a contento!
    Muito bacana!

  20. gabriel disse:

    Muito Bom! Resolveu aqui!.

  21. Cristiano disse:

    Muito Show! Apenas um detalhe, tem que concatenar o path com o nome do arquivo antes de fazer a verificação se o arquivo existe…
    Parabéns pelo código.

  22. claudiney disse:

    o script funcionou só que o arquivo fica corrompido, como resolvo isto?

  23. Luiz Henrique disse:

    Muito bom cara, funcionou direitinho, obrigado

  24. Laércio disse:

    Ótimo artigo, ajudou-me muito.

  25. MARCIO disse:

    Cara Boa Tarde.
    Copei o seu código aCima salvei Como download.php subi o código só alterei "o diretório downloads pra o musiCa Como abaixo mas não funCiona:
    <?php
    define('DIR_DOWNLOAD', '../musica/'); // 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 <span class="si1e2pdjxum" id="si1e2pdjxum_16">diretório com</span> o nome do arquivo

    header('Content-type: octet/stream');
    header('Content-disposition: attachment; filename="'.basename($arquivo).'";');
    header('Content-Length: '.filesize($arquivo));
    readfile($arquivo);
    exit;
    ?>

    Não realiza o Download apraCe direto "Operação não permitida."
    ALGUEM PODE ME AJUDAR POR FAVOR…. DESDE JÁ AGRADEÇO

Deixe uma resposta