Ir para conteúdo
Omar~

Até onde posso confiar na classe finfo

Recommended Posts

Boas?!

Como o próprio título diz posso realmente confiar em obter o mime-type do arquivo através da classe finfo?

 

Ao logo dos anos sempre para uploads de arquivos para o servidor, como os sistemas que eu desenvolvi um upload só podia ser realizado por um administrador, apenas verificava a extensão do arquivo através da chave "type" na super global $_FILES.

Mas sempre soube que não podia confiar em tal.

 

No mais simples podemos enganar a chave "type" criando um arquivo de texto qualquer e salvando com a extensão .JPG se consultamos por exemplo $_FILES['upload']['type'] teremos então o mime-type image/jpg mesmo o arquivo de fato sendo um .TXT

(A proposta não se trata só de imagens mas qualquer tipo de arquivo, apenas usei uma imagem para exemplificar)

 

Mas usando a classe finfo essa obtive o correto modelo de arquivo.

<?php
$finfo = new finfo(FILEINFO_MIME);
$mimeType = $finfo->file($_FILES['upload']['tmp_name']);

echo "<pre>";
var_dump(
  substr($mimeType, 0, strpos($mimeType, ';')),
  $_FILES['upload']['type']
);

 

Mas seria possível enganar o método?

 

Obs.: Se houver como não poste código nesse contexto, apenas quero saber de possíveis vulnerabilidades dessa classe nativa que até então nunca utilizei.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Fala @Omar~, tudo certo?

 

Vamos lá:

7 horas atrás, Omar~ disse:

posso realmente confiar em obter o mime-type do arquivo através da classe finfo?

Mais ou menos. Pelo menos não de olhos fechados.

Segurança para upload de arquivos é um assunto um pouco mais profundo. Não basta se preocupar com o mime do arquivo, pois ele é manipulável.

Veja um exemplo (imagem png com código php/js embutido): https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

Lembre-se de que camuflar arquivos maliciosos em arquivos aparentemente inofensivos não é exclusivo a aplicações web. Os anti-vírus estão há decadas criando soluções para evitar esse tipo de problema.

A questão é: você se preocupa que um usuário baixe um vírus enviado anteriormente por um usuário, ou apenas se preocupa com a segurança de sua aplicação? Pois para o primeiro caso, eu iria sugerir que você rodasse em uma solução como no virustotal, por exemplo, mas isso seria no caso de permitir aos usuários que enviassem arquivos mais genéricos como zip, rar, 7zip, iso, exe, msi, gzip, tar, rpm, etc. É um ponto que não devemos ignorar, mas acredito que suas preocupações estejam na aplicação em si, então vamos prosseguir:

 

Você não deve confiar no $_FILES, pois ele é literalmente informado pelo navegador, mas isso você já sabia.

A partir do momento que você passa a utilizar o módulo Fileinfo (finfo), você transfere a responsabilidade de analisar o conteúdo do arquivo ao servidor, e isso promove sua aplicação de 10% de segurança para uns bons 50~60% de segurança (no que tange o upload dos arquivos).

 

Você poderia ainda, no entanto, se preocupar com alguns pontos importantes:

 

1 - Não nomear os arquivos enviados com extensões sugeridas pelo nome original do arquivo.

Exemplo do arquivo enviado: meu_arquivo.php.png

Essa dupla extensão pode ser explorada através de Null byte injection ou mod_mime do PHP (que pode processar arquivos PHP com dupla extensão).

Então vamos supor que você upou o arquivo com o nome acima. Caso o mod_mime não o ajude arbitrariamente, você poderia ainda assim processá-lo como PHP assim:

http://site.com/uploads/meu_arquivo.php%00

Uma boa prática seria aplicar um UUID e uma tabela de extensões. Exemplo:

Mime: image/jpeg
Arquivo: 123e4567-e89b-12d3-a456-426614174000.jpg

Perceba que .jpg não é por causa do nome original do arquivo, mas pq eu inferi através do mime.
Isso você faria com um array associativo de mimes/extensões, por exemplo.

Caso você não queira modificar o nome do arquivo, não tem problema, mas não permita caracteres fora do range do alfabeto, números e alguns poucos caracteres como underscore "_" ----> [a-zA-Z0-9_-]

Isso já iria automaticamente remover as extensões, uma vez que você estaria removendo os pontos.

 

Por algum tempo em uma aplicação eu literalmente não aplicava extensão nenhuma nos arquivos. Os arquivos tinham nomes assim:

8ats9d786astn9d8
8nyda08f7tn2h98z
...

Eu fazia isso em uma CDN onde eu informava o mime/type na resposta da requisição, nunca gerando problemas para acessá-los.

Mas quis apenas compartilhar mesmo, pois não é uma boa prática. Deixei de fazer isso depois que tive problemas de cache por parte da Cloudflare, que não reconhecia os arquivos corretamente.

Apenas não confie na extensão que o usuário informa, mas aplique uma extensão coerente ao conteúdo do arquivo, baseada no MIME.

 

2 - Bom, mime verificado, extensão não influenciada, o próximo passo é pensarmos: qual o real risco para a minha aplicação se alguém enviar um arquivo contendo códigos que possa ser interpretado pelo servidor?

O risco são na verdade dois:

- LFI, ou Local File Inclusion. É quando temos inclusões variáveis com dados originados na requisição, como nesse exemplo:

<?php
$pagina = $_GET[ 'pag' ];

include( "paginas/{$pagina}.php" );

Daí você acessa o site assim:

http://site.com/?pag=../../../../../etc/passwd%00
Ou:
http://site.com/?pag=../uploads/arquivo_que_acabei_de_enviar

Isso é facilmente corrigido filtrando a string $pagina, mas você sabe disso.

 

- O outro caso seria de literalmente acessarmos o arquivo como no exemplo do primeiro link que citei nesse post:

http://example.org/images/test.png?zz=alert("this is xss :(");

Observe neste exemplo que o arquivo chamado é o próprio enviado, e ele inclusive é um png, mas com PHP injetado.

Isso poderia ser evitado, mesmo com um png malicioso, desativando a execução do PHP nesse diretório, o que torna todo aquele esforço do artigo sem sentido, entende? É aí que vamos diminuindo drasticamente os riscos de brechas de segurança.

Isso poderia ser feito criando um .htaccess (falando de Apache. Outros webservers = outras soluções) no diretório com o seguinte conteúdo:

php_flag engine off

Veja mais: http://docs.php.net/manual/en/apache.configuration.php#ini.engine

 

Em muitos artigos você vai encontrar as seguinte sugestões ainda, que se você analisar com calma, vai perceber que são apenas redundâncias para as medidas que já citei acima (mas é bom lembrar que redundância em segurança não é nada ruim!):

  • Varrer o arquivo para verificar se possui abertura de tags PHP "<?", mas acho um método muito falho, pois é abrangente demais e pode acabar gerando falsos-positivos.
  • Reprocessar as imagens usando GD ou imagick. Também não é uma solução absoluta, pois podemos ter comentários e metadados em arquivos de imagens que podem ser explorados com a finalidade de injetar códigos maliciosos, mas ajuda, principalmente se você chamar essas funções habilitando configurações de compressão e remoção de lixo. A questão é que isso só é válido para imagens, o que não é o seu caso.
  • Criar uma espécie de pequeno web firewall, onde você colocaria seus arquivos em uma pasta absolutamente fora de public e os traria apenas quando solicitado, manualmente, via código PHP, com readfile, por exemplo. Na minha opinião, um grande desperdício de recursos de máquina e tempo de programação em cima disso, mas não deixa de ter o seu valor.
  • Ler o cabeçalho do arquivo (primeiros ~20 bytes) e comparar com a tabela Magic, mas isso é exatamente o que o Fileinfo faz. Você estaria sendo redundante e perdendo tempo com algo que não deveria ser uma preocupação sua.

 

Mas o mais importante eu guardei para a conclusão: não salve os arquivos no mesmo servidor que processa a lógica da aplicação!

Por diversas razões, e não apenas por segurança. Você desacopla seu código (lógica) de dados de execução, o que é um grande avanço para aplicações escaláveis (que podem crescer indefinidamente em quantidade de máquinas), uma vez que os acessos aos arquivos agora seriam apenas urls externas de servidores que apenas entregam arquivos, e não arquivos armazenados na mesma máquina. Exemplo:

http://site.com.br/uploads/arquivo.xls

 

Agora seria:

http://arquivos.site.com.br/arquivo.xls

 

Perceba que arquivos.site.com.br não é apenas um subdomínio ou redirecionamento, mas um servidor à parte, que pode ser universalmente utilizado por todas as máquinas.

E como você faria isso? É muito difícil? Muito caro?

 

Fácil e barato: S3 + Cloudflare.

Custos do S3: https://aws.amazon.com/pt/s3/pricing/

Cloudflare pode ser o plano gratuito.

 

Citei o S3 pois estou mais acostumado, mas tem ainda as soluções da Microsoft (Azure), Google Cloud Computing, e aquele outro que tem nome de Pokémon, mas não lembro agora =P

 

Basta mandar os arquivos para o S3 e depois aplicar o cache pela Cloudflare. Isso garantirá custos baixos de tráfego se os arquivos não forem muito grandes e estiverem dentro da cota de cache da Cloudflare.

Digo isso pois manter os arquivos na S3 é barato, mas o custo do tráfego pode surpreender dependendo do seu uso. Por isso a cloudflare ajuda (e muito).

 

Resumindo: Fileinfo, junto a boas práticas para evitar vulnerabilidades no upload vão te dar uma confiabilidade altíssima, mas construir uma pequena CDN e mandar arquivos para serem servidos por ela leva seu app a outro nível com muito mais facilidade :)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Blz @Matheus Tavares 

Gostei de certos pontos que você citou, como varrer o binário do arquivo em busca de códigos, mas penso que isso poderia custar muito em desempenho e sacrificar a usabilidade.

Sobre usar um diretório reconfigurando o apache com htaccess para não processar PHP (Dessa eu não sabia que era possível ^^)

Mas o fato que mais me predeu atenção é salvar os arquivos em outro servidor.

 

Quanto ao caso de nome do arquivo, imagem em geral até mesmo sua extensão isso é "LEI ter de se fazer" então sempre apliquei essas condutas, no caso de imagem ou sempre a recrio "até mesmo para fazer a correção da orientação" consigo me livrar de códigos maliciosos na mesma.

 

A questão do mime-type era justamente o que fazer com o arquivo que o usuário enviar, sem que ele possa enganar dizendo que um arquivo é uma coisa quando na verdade é outra.

Na aplicação aqui o usuário pode enviar "qualquer coisa".

  • Se for imagem > recrio ela e salvo
  • Se for arquivo comprimido (zip/7z/bz/ra) > Salvo o mesmo sem fazer verificações apenas altero seu nome quando salvo "Acredito eu que não seja possível embutir código nesse tipo de arquivo". Talvez esse seja o ponto que eu esteja pecando na vulnerabilidade.
  • Se for qualquer outro tipo > estou compactando com ZipArchive uma vez que isso é gerado pelo meu código e apenas pego o conteúdo temporário e insiro dentro do arquivo ZIP criado, acredito que não há problemas.

 

Como você mesmo disse... Minha preocupação não está com o usuário fazer download de um suposto arquivo contaminado (mesmo que deveria) e sim na segurança da aplicação.

Pois em meu testes justamente enviei um arquivo para quebrar meu código PHP disfarçado de outro tipo de arquivo o que deu certo, e como dessa vez a aplicação que estou a fazer vai poder receber arquivos até do "zé da silva" me fez prender a atenção sobre a integridade do código nesse requisito...

Mesmo porque sou paranoico-e-meio-tanto com segurança.

 

A questão mesmo era confiar na classe, a qual é a primeira vez que uso, então passei o dia todo rodando a internet em busca de experiências com a mesma. Até porque a documentação do PHP é muito branda sobre essa classe.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.