Aceitar ficheiros de estranhos sem levar com um susto
Um formulário de upload é um convite para pôr ficheiros no seu servidor. Vale a pena aceitar com regras.
1. A extensão não prova nada
Mudar virus.php para foto.jpg demora um segundo. O que se verifica é o conteúdo: os primeiros bytes de um ficheiro dizem o que ele é.
// Assinaturas: os primeiros bytes de cada formato
const ASSINATURAS = {
'image/jpeg': [0xff, 0xd8, 0xff],
'image/png': [0x89, 0x50, 0x4e, 0x47],
'application/pdf': [0x25, 0x50, 0x44, 0x46],
};
const cabeca = new Uint8Array(await ficheiro.slice(0, 4).arrayBuffer());
const tipo = Object.entries(ASSINATURAS).find(([, assinatura]) =>
assinatura.every((byte, i) => cabeca[i] === byte)
);
if (!tipo) throw new Error('Formato não aceite.');O Content-Type que vem no pedido também não prova nada: quem envia é que o escreve.
2. Um limite de tamanho, antes de ler
Sem limite, um ficheiro de 40 GB enche o disco ou a memória. O limite tem de estar no servidor e no proxy à frente — o do browser é uma sugestão.
client_max_body_size 10M;3. O nome do ficheiro é do atacante
Nunca use o nome que vem no pedido. Ele pode conter ../ e escrever fora da pasta pretendida, ou ter 300 caracteres, ou chamar-se .htaccess.
import { randomUUID } from 'node:crypto';
const nome = `${randomUUID()}.jpg`; // o nome original guarda-se na base, se interessar4. Onde os ficheiros ficam
Fora da pasta servida pelo site. Se estiverem num sítio que o servidor web serve diretamente, um ficheiro com o nome certo pode acabar por ser executado.
Servidos por uma rota que verifica permissões, ou por um armazenamento externo com endereços assinados e validade curta.
Com o cabeçalho certo à saída, para o browser não adivinhar:
Content-Typecorreto eX-Content-Type-Options: nosniff.
5. Imagens não são inofensivas
Um SVG é XML, e XML pode ter JavaScript lá dentro — um SVG servido do seu domínio é um XSS. Ou se recusa o formato, ou se sanitiza como se sanitiza HTML. E vale a pena reprocessar as imagens aceites: além de as normalizar, isso deita fora os metadados EXIF, que costumam trazer as coordenadas GPS de onde a fotografia foi tirada.