Security Academy→Course→File Upload Security
Modul 8 / 12

File Upload Security

Dokumenter, bilder, GIS-data: type, størrelse, navn, lagring og tilgang.

Hvorfor frontend-sjekk ikke holder

accept=".pdf" og file.type er satt av browseren/brukeren. Requesten kan sendes med hvilket som helst innhold, navn og Content-Type.

  • ›File type: sjekk magic bytes på server, ikke bare extension
  • ›MIME type: Content-Type er brukerstyrt; sett den selv ved lagring
  • ›Extension: allowlist (.pdf, .png, .geojson), aldri blocklist
  • ›File size: grense på server og i bucket-konfig
  • ›Filename: generer egen nøkkel (uuid), lagre originalnavn som metadata
  • ›SVG og HTML: kan inneholde script — servér med Content-Disposition: attachment eller fra eget domene
  • ›Executables/arkiver: avvis eller skann; zip-bomber for GIS-pakker (shapefile .zip)
  • ›Metadata: EXIF kan inneholde GPS — strip for bilder som deles
  • ›Storage permissions: privat bucket, signed URLs med kort levetid
  • ›Authorisation: storage-policies som sjekker eierskap på path (userId/...)

Storage-policy

Vulnerable · sql
-- Offentlig bucket, alle kan laste opp hvor som helst
insert into storage.buckets (id, name, public) values ('docs','docs', true);
create policy "upload" on storage.objects
  for insert to authenticated with check (bucket_id = 'docs');
Secure · sql
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
values ('docs','docs', false, 10485760, array['application/pdf','image/png']);

create policy "own folder write" on storage.objects
  for insert to authenticated
  with check (bucket_id = 'docs'
    and (storage.foldername(name))[1] = auth.uid()::text);

create policy "own folder read" on storage.objects
  for select to authenticated
  using (bucket_id = 'docs'
    and (storage.foldername(name))[1] = auth.uid()::text);

Testene

  • ›Last opp test.html med <script>console.log(1)</script> omdøpt til .pdf — åpnes den som HTML?
  • ›Last opp SVG med <script> — kjøres den når URL-en åpnes direkte?
  • ›Last opp til en annen brukers mappe (endre path i request)
  • ›Åpne en fil-URL i et inkognitovindu — er den tilgjengelig uten login?
  • ›Last opp fil over grensen via curl, forbi UI
Kontroller i din egen app
Labs for denne modulen

Quiz

Scenario 1

Opplastingsskjemaet har accept="image/*" og sjekker file.type.startsWith('image/'). Bucket er offentlig. Er HTML-opplasting mulig?

Scenario 2

Brukere laster opp GeoJSON som vises på et kart med popup som viser feature.properties.name via innerHTML. Risiko?