Como implementar o upload de ficheiros para um servidor da forma correta: questões de segurança e escalabilidade

Quem já usou redes sociais, já deve ter reparado nas fotos de perfil. Normalmente, existe uma função que permite mudar essa foto de perfil, atualizar a foto de perfil e até mesmo apagar a foto de perfil. O Facebook, o Instagram, o WhatsApp tem essas funcionalidades.

A questão é que criar a funcionalidade de upload de imagens é simples, mas fazer a funcionalidade de forma correta é um bocado mais complicado e existem coisas que devem ser pensadas. Como utilizador, quero mudar a minha foto de perfil. Mas também como utilizador, quero que a minha foto de perfil esteja em segurança.

A verdade é que a primeira coisa que temos de assumir é que uma vez na Internet para sempre na Internet. É possível resgatar fotos apagadas só que isto já é outro assunto para outro artigo.

É possível pesquisar a foto de perfil do LinkedIn no Google Images. Se pesquisarem o vosso nome completo no Google Images, a foto de perfil deverá aparecer em algum lugar. Isso são os chamados ficheiros estáticos. Para armazenar esses ficheiros estáticos, podemos usar CDN’s (Content Delivery Networks). Estas redes são distribuídas geograficamente e permitem acesso rápido e seguro a conteúdos como vídeos, imagens, páginas estáticas, ficheiros de código, documentos (Cloudflare, 2023) …

Desta forma garante-se a segurança dos dados que é o caso que acontece com a AWS, a Cloudflare e entre outros. No entanto há ainda outras coisas que podem ser melhoradas.

Há uns meses, assisti a uma palestra de um engenheiro da Bosch na minha universidade que a sua função era Ethical Hacker. Recomendo aos leitores do blogue, assistirem a esta palestra que vai exatamente de encontro ao assunto que este artigo aborda. Isto é, com técnicas de OSINT, é possível aceder a dados desprotegidos (IPCA, 2023)

Palestra do engenheiro da Bosch: https://www.youtube.com/watch?v=SztXU5ZMDJo

Então, como é que protegemos o acesso a estes recursos estáticos?

Pode-se usar as CDN’s, mas também é preciso garantir que o servidor que está a ser desenvolvido seja pensado para a segurança de dados.

Primeiro ponto importante, deve-se ter um sistema de autenticação seguro. Pode ser usado tokens de sessão temporários e de pouca duração para obrigar o utilizador a se autenticar novamente para ter acesso ao recurso.

Em segundo lugar, deve-se garantir a integridade do ficheiro que é carregado para o servidor através da comparação das hashes do ficheiro de origem com o ficheiro carregado. Ao enviar o pedido para o servidor tudo pode acontecer no meio. Se o ficheiro for modificado, significa que alguém intercetou o ficheiro e adicionou código malicioso e esse ficheiro deve ser imediatamente apagado do servidor.

Em terceiro lugar, no caso das fotos de perfil de um utilizador, se outro utilizador conhecer o servidor de ficheiros e o nome de outro utilizador, pode buscar facilmente a foto do outro utilizador. Isso pode ser uma falha de segurança. O que se pode fazer neste caso é “cifrar” o nome de utilizador e colocar a “cifra” no nome do ficheiro. É mais difícil aceder a esses ficheiros. Para melhorar, pode-se criar uma outra rota para descarregar o ficheiro. No entanto, perde-se na escalabilidade do servidor. A rota não seria para descarregar, mas sim seria para retornar uma URI assinada para outro servidor e a aplicação é que vai buscar a foto ao servidor de ficheiros. Mas aqui o backend vai buscar apenas a foto daquele utilizador e só o backend conhece o servidor de ficheiros.

Ou seja, isto funcionará com dois servidores.

Existe diversas formas de garantir a segurança. Agora, será demonstrada a implementação disto com o exemplo das fotos de perfil.

No pseudocódigo seguinte encontra-se representado como é que funcionará o upload:



Neste exemplo espera-se que exista um serviço de autenticação registo e login. Implementou-se o serviço de autenticação e login que não será abordado aqui.

Os códigos seguintes foram feitos em Node.js, mas a lógica é replicável em qualquer runtime (.NET Core, Flask,  Django…)

O que se fez primeiro foi instalar um pacote npm chamado “multer”. Este pacote lida com formulários multipart/form-data. Estes formulários permitem o upload de ficheiros. Podia-se usar JSON, mas teria de se colocar no corpo o código Base64 do ficheiro e isso é pesado e não vale a pena estar a criar complicação desnecessária.



Após a configuração da autenticação, criou-se um ficheiro de configuração para o multer:


Na constante storage é definido a pasta de destino “./uploads”

No filename é verificado se o utilizador está autenticado e gerado a “cifra do ficheiro”. Para este caso usou-se uuids porque ao usar bcrypt, os nomes de ficheiro ficam com caracteres especiais e isso baralha totalmente o sistema operativo (seja Windows, Linux ou MacOS).

Depois é criado o middleware express para o multer em linha 18.

O fileFilter é uma função que permite limitar as extensões de ficheiro:

No caso serão verificados os mimetypes de cada ficheiro o que é correto, porque uma imagem pode ser .html e ser do mimetype image/png e sendo que uma extensão pode ter vários mimetypes. O .jpg pode ter o mimetype image/jpg e image/jpeg

De seguida, foi criado um gerador de checksums:


Nesta função é criado o checksum de um ficheiro existente no servidor

Agora falta implementar a funcionalidade no servidor. Na imagem seguinte encontra-se  a implementação da mudança de foto.


 


É verificada se o utilizador está autenticado. Se estiver, ele procura o utilizador na base de dados. Se existir, o upload pode ser realizado.

Em multerMiddleware é verificado a existência de algum erro após o upload. Se houver erro, devolve 400. Senão, o ficheiro é renomeado e criado o checksum.

Tendo sido gerado o checksum, é verificado se os checksums do ficheiro do cliente e do servidor são os mesmos:



Caso não sejam, o ficheiro é imediatamente apagado e devolvido código 409. Em caso de sucesso, a foto é salva na base de dados como url e é devolvido o código de sucesso 200 OK.

Ao testar, criou-se um checksum de uma imagem aleatória:


O site onde se retirou o checksum dessa imagem foi este:  https://emn178.github.io/online-tools/sha256_checksum.html

No caso, o checksum tem de vir do cliente no corpo do pedido senão, não funcionará.


Ao simular o checksum diferente, acontece isto:



Isto significa que o checksum do ficheiro final não é o mesmo simulando quando um hacker modifica o ficheiro.

No caso de sucesso, se agora copiarmos o caminho do ficheiro e apontarmos esse caminho no servidor, teremos acesso ao ficheiro:



Como é possível verificar temos acesso ao recurso pretendido. No entanto esta lógica é simples e para melhorar este pequeno programa o mais interessante seria guardar esse ficheiro num servidor externo de ficheiros, isto é, teríamos de passar este caminho num request axios para o servidor pretendido e enviar esse ficheiro para esse servidor. No entanto para este exemplo, foi decidido não complicar, mas para a questão da escalabilidade, estes ficheiros estariam num servidor Apache ou Nginx ou um servidor HTTP ou um servidor FTP. Desta forma, melhora-se a escalabilidade do sistema, pois armazenar esses ficheiros no servidor torna-o pesado.


Portanto a arquitetura escalável deste sistema seria esta:




Este artigo teve o propósito de abordar as questões da segurança da informação em upload de ficheiros. Desta forma é mais difícil haver falhas de segurança no sistema, garantindo a autenticação, a integridade dos ficheiros enviados e a maior dificuldade em aceder aos ficheiros de outros. É demonstrado que a arquitetura implementada não é escalável e além disso acaba por não ser segura. Na imagem anterior encontra-se uma forma mais segura e escalável de carregar ficheiros para um servidor. A segurança nos sistemas de informação torna-se um fator importante e da forma que é demonstrada na imagem anterior, é mais seguro evitando que quando alguém com conhecimentos OSINT (Open Source Intelligence) vasculhe ficheiros confidenciais, tal como poderão ver na palestra. Cuidado com isto. E mais importante, nenhum sistema é totalmente seguro e cabe aos programadores de servidores terem noções de segurança, como não guardar passwords no código, as passwords devem ser cifradas e guardadas na base de dados, acessos a servidores e bases de dados devem estar em variáveis de ambiente, uploads de ficheiros devem ser seguros desta ou usando CDN's como referido no início.

Referencias:

Cloudflare. (2023). What is a CDN? | How do CDNs work? | Cloudflare. Cloudflare.

https://www.cloudflare.com/learning/cdn/what-is-a-cdn/

 IPCA. (2023). OSINT - BEWARE YOUR DATA IS OUT THERE. Www.youtube.com; IPCA Barcelos. https://www.youtube.com/watch?v=SztXU5ZMDJo